capdebug 0.1.0 → 0.3.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 (135) hide show
  1. package/README.md +69 -1
  2. package/android/build/.transforms/105231053f5053e70f17f15178bed793/results.bin +1 -0
  3. package/android/build/.transforms/7c2b02c40854250ff97be5e9e723091e/results.bin +1 -0
  4. package/android/build/.transforms/ca1c0f6bb67b5238fd83a40c3a1c0ddd/results.bin +1 -0
  5. package/android/build/.transforms/ca1c0f6bb67b5238fd83a40c3a1c0ddd/transformed/classes/classes_dex/classes.dex +0 -0
  6. package/android/build/intermediates/aapt_friendly_merged_manifests/debug/aapt/AndroidManifest.xml +9 -0
  7. package/android/build/intermediates/aapt_friendly_merged_manifests/debug/aapt/output-metadata.json +18 -0
  8. package/android/build/intermediates/aar_metadata/debug/aar-metadata.properties +6 -0
  9. package/android/build/intermediates/annotation_processor_list/debug/annotationProcessors.json +1 -0
  10. package/android/build/intermediates/compile_library_classes_jar/debug/classes.jar +0 -0
  11. package/android/build/intermediates/compile_r_class_jar/debug/R.jar +0 -0
  12. package/android/build/intermediates/compile_symbol_list/debug/R.txt +0 -0
  13. package/android/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties +1 -0
  14. package/android/build/intermediates/incremental/debug/packageDebugResources/merger.xml +2 -0
  15. package/android/build/intermediates/incremental/mergeDebugJniLibFolders/merger.xml +2 -0
  16. package/android/build/intermediates/incremental/mergeDebugShaders/merger.xml +2 -0
  17. package/android/build/intermediates/incremental/packageDebugAssets/merger.xml +2 -0
  18. package/android/build/intermediates/java_res/debug/out/META-INF/capdebug_debug.kotlin_module +0 -0
  19. package/android/build/intermediates/local_only_symbol_list/debug/R-def.txt +2 -0
  20. package/android/build/intermediates/manifest_merge_blame_file/debug/manifest-merger-blame-debug-report.txt +11 -0
  21. package/android/build/intermediates/merged_manifest/debug/AndroidManifest.xml +9 -0
  22. package/android/build/intermediates/navigation_json/debug/navigation.json +1 -0
  23. package/android/build/intermediates/runtime_library_classes_jar/debug/classes.jar +0 -0
  24. package/android/build/intermediates/symbol_list_with_package_name/debug/package-aware-r.txt +1 -0
  25. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab +0 -0
  26. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.keystream +0 -0
  27. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.keystream.len +0 -0
  28. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.len +0 -0
  29. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.values.at +0 -0
  30. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab_i +0 -0
  31. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab_i.len +0 -0
  32. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab +0 -0
  33. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.keystream +0 -0
  34. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.keystream.len +0 -0
  35. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.len +0 -0
  36. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.values.at +0 -0
  37. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab_i +0 -0
  38. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab_i.len +0 -0
  39. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab +0 -0
  40. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream +0 -0
  41. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream.len +0 -0
  42. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.len +0 -0
  43. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.values.at +0 -0
  44. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab_i +0 -0
  45. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab_i.len +0 -0
  46. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab +0 -0
  47. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream +0 -0
  48. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream.len +0 -0
  49. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.len +0 -0
  50. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.values.at +0 -0
  51. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i +0 -0
  52. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i.len +0 -0
  53. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab +0 -0
  54. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.keystream +0 -0
  55. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.keystream.len +0 -0
  56. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.len +0 -0
  57. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.values.at +0 -0
  58. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab_i +0 -0
  59. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab_i.len +0 -0
  60. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab +0 -0
  61. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.keystream +0 -0
  62. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.keystream.len +0 -0
  63. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.len +0 -0
  64. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.values.at +0 -0
  65. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab_i +0 -0
  66. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab_i.len +0 -0
  67. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab +0 -0
  68. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.keystream +0 -0
  69. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.keystream.len +0 -0
  70. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.len +0 -0
  71. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.values.at +0 -0
  72. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab_i +0 -0
  73. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab_i.len +0 -0
  74. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab +0 -0
  75. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.keystream +0 -0
  76. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.keystream.len +0 -0
  77. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.len +0 -0
  78. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.values.at +0 -0
  79. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab_i +0 -0
  80. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab_i.len +0 -0
  81. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/counters.tab +2 -0
  82. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab +0 -0
  83. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.keystream +0 -0
  84. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.keystream.len +0 -0
  85. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.len +0 -0
  86. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.values.at +0 -0
  87. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab_i +0 -0
  88. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab_i.len +0 -0
  89. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab +0 -0
  90. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.keystream +0 -0
  91. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.keystream.len +0 -0
  92. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.len +0 -0
  93. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.values.at +0 -0
  94. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab_i.len +0 -0
  95. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab +0 -0
  96. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream +0 -0
  97. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream.len +0 -0
  98. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.len +0 -0
  99. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.values.at +0 -0
  100. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab_i +0 -0
  101. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab_i.len +0 -0
  102. package/android/build/kotlin/compileDebugKotlin/cacheable/last-build.bin +0 -0
  103. package/android/build/kotlin/compileDebugKotlin/classpath-snapshot/shrunk-classpath-snapshot.bin +0 -0
  104. package/android/build/kotlin/compileDebugKotlin/local-state/build-history.bin +0 -0
  105. package/android/build/outputs/logs/manifest-merger-debug-report.txt +20 -0
  106. package/android/build/tmp/kotlin-classes/debug/META-INF/capdebug_debug.kotlin_module +0 -0
  107. package/android/build/tmp/kotlin-classes/debug/com/capdebug/plugin/CapDebugPlugin$Companion.class +0 -0
  108. package/android/build/tmp/kotlin-classes/debug/com/capdebug/plugin/CapDebugPlugin.class +0 -0
  109. package/android/src/main/java/com/capdebug/plugin/CapDebugPlugin.kt +54 -0
  110. package/bin/capdebug.js +8 -0
  111. package/dist/capacitor/deep-link.d.ts +4 -0
  112. package/dist/cli/index.d.ts +1 -0
  113. package/dist/cli/qr.d.ts +15 -0
  114. package/dist/cli/server.d.ts +34 -0
  115. package/dist/cli.js +1868 -0
  116. package/dist/cli.js.map +1 -0
  117. package/dist/index.d.ts +13 -0
  118. package/dist/plugin.cjs.js +2639 -1912
  119. package/dist/plugin.cjs.js.map +1 -1
  120. package/dist/plugin.js +2749 -2022
  121. package/dist/plugin.js.map +1 -1
  122. package/dist/plugin.mjs +2632 -1913
  123. package/dist/plugin.mjs.map +1 -1
  124. package/dist/remote/buffer.d.ts +13 -0
  125. package/dist/remote/desktop-viewer-html.d.ts +10 -0
  126. package/dist/remote/pairing-page-html.d.ts +8 -0
  127. package/dist/remote/pairing.d.ts +34 -0
  128. package/dist/remote/protocol.d.ts +14 -0
  129. package/dist/remote/remote-manager.d.ts +19 -0
  130. package/dist/remote/signaling.d.ts +27 -0
  131. package/dist/remote/transport.d.ts +15 -0
  132. package/dist/remote/types.d.ts +69 -0
  133. package/dist/remote/webrtc-transport.d.ts +47 -0
  134. package/ios/Sources/CapDebugPlugin/CapDebugPlugin.swift +41 -0
  135. package/package.json +6 -2
@@ -4,843 +4,773 @@ Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  var core = require('@capacitor/core');
6
6
 
7
- const DEFAULT_SENSITIVE_KEYS = new Set([
8
- 'authorization',
9
- 'cookie',
10
- 'set-cookie',
11
- 'password',
12
- 'passwd',
13
- 'pass',
14
- 'token',
15
- 'access_token',
16
- 'accesstoken',
17
- 'refresh_token',
18
- 'refreshtoken',
19
- 'api_key',
20
- 'apikey',
21
- 'secret',
22
- 'client_secret',
23
- 'clientsecret',
24
- 'bearer',
25
- 'private_key',
26
- 'privatekey',
27
- 'session_id',
28
- 'sessionid',
29
- 'jwt',
30
- 'credit_card',
31
- 'creditcard',
32
- 'cvv',
33
- 'ssn',
7
+ const DEFAULT_SENSITIVE_KEYS = /* @__PURE__ */ new Set([
8
+ "authorization",
9
+ "cookie",
10
+ "set-cookie",
11
+ "password",
12
+ "passwd",
13
+ "pass",
14
+ "token",
15
+ "access_token",
16
+ "accesstoken",
17
+ "refresh_token",
18
+ "refreshtoken",
19
+ "api_key",
20
+ "apikey",
21
+ "secret",
22
+ "client_secret",
23
+ "clientsecret",
24
+ "bearer",
25
+ "private_key",
26
+ "privatekey",
27
+ "session_id",
28
+ "sessionid",
29
+ "jwt",
30
+ "credit_card",
31
+ "creditcard",
32
+ "cvv",
33
+ "ssn"
34
34
  ]);
35
- const MASK_VALUE = '********';
35
+ const MASK_VALUE = "********";
36
36
  class DataSanitizer {
37
- maskKeys;
38
- maxDepth;
39
- maxArrayLength;
40
- constructor(options = {}) {
41
- this.maskKeys = new Set([
42
- ...DEFAULT_SENSITIVE_KEYS,
43
- ...(options.customMaskKeys || []).map(k => k.toLowerCase().trim()),
44
- ]);
45
- this.maxDepth = options.maxDepth ?? 10;
46
- this.maxArrayLength = options.maxArrayLength ?? 100;
47
- }
48
- isSensitiveKey(key) {
49
- const normalized = key.toLowerCase().replace(/[-_]/g, '');
50
- if (this.maskKeys.has(key.toLowerCase()) || this.maskKeys.has(normalized)) {
51
- return true;
52
- }
53
- for (const sensitive of this.maskKeys) {
54
- const cleanSensitive = sensitive.replace(/[-_]/g, '');
55
- if (normalized.includes(cleanSensitive) && cleanSensitive.length >= 4) {
56
- return true;
57
- }
58
- }
59
- return false;
37
+ maskKeys;
38
+ maxDepth;
39
+ maxArrayLength;
40
+ constructor(options = {}) {
41
+ this.maskKeys = /* @__PURE__ */ new Set([
42
+ ...DEFAULT_SENSITIVE_KEYS,
43
+ ...(options.customMaskKeys || []).map((k) => k.toLowerCase().trim())
44
+ ]);
45
+ this.maxDepth = options.maxDepth ?? 10;
46
+ this.maxArrayLength = options.maxArrayLength ?? 100;
47
+ }
48
+ isSensitiveKey(key) {
49
+ const normalized = key.toLowerCase().replace(/[-_]/g, "");
50
+ if (this.maskKeys.has(key.toLowerCase()) || this.maskKeys.has(normalized)) {
51
+ return true;
52
+ }
53
+ for (const sensitive of this.maskKeys) {
54
+ const cleanSensitive = sensitive.replace(/[-_]/g, "");
55
+ if (normalized.includes(cleanSensitive) && cleanSensitive.length >= 4) {
56
+ return true;
57
+ }
58
+ }
59
+ return false;
60
+ }
61
+ sanitize(value) {
62
+ const seen = /* @__PURE__ */ new WeakSet();
63
+ return this.sanitizeRecursive(value, 0, seen);
64
+ }
65
+ sanitizeRecursive(value, depth, seen) {
66
+ if (value === null || value === void 0) {
67
+ return value;
60
68
  }
61
- sanitize(value) {
62
- const seen = new WeakSet();
63
- return this.sanitizeRecursive(value, 0, seen);
69
+ if (depth > this.maxDepth) {
70
+ return "[Max Depth Reached]";
64
71
  }
65
- sanitizeRecursive(value, depth, seen) {
66
- if (value === null || value === undefined) {
67
- return value;
68
- }
69
- if (depth > this.maxDepth) {
70
- return '[Max Depth Reached]';
71
- }
72
- const type = typeof value;
73
- if (type === 'string' || type === 'number' || type === 'boolean') {
74
- return value;
75
- }
76
- if (type === 'bigint') {
77
- return value.toString() + 'n';
78
- }
79
- if (type === 'symbol') {
80
- return value.toString();
81
- }
82
- if (type === 'function') {
83
- return `[Function: ${value.name || 'anonymous'}]`;
84
- }
85
- if (typeof value === 'object') {
86
- // Check circular reference
87
- if (seen.has(value)) {
88
- return '[Circular Reference]';
89
- }
90
- seen.add(value);
91
- // Error handling
92
- if (value instanceof Error) {
93
- return {
94
- name: value.name,
95
- message: value.message,
96
- stack: value.stack,
97
- ...value,
98
- };
99
- }
100
- // Date handling
101
- if (value instanceof Date) {
102
- return value.toISOString();
103
- }
104
- // RegExp handling
105
- if (value instanceof RegExp) {
106
- return value.toString();
107
- }
108
- // DOM Node handling
109
- if (typeof Element !== 'undefined' && value instanceof Element) {
110
- const id = value.id ? `#${value.id}` : '';
111
- const className = value.className ? `.${String(value.className).trim().replace(/\s+/g, '.')}` : '';
112
- return `<${value.tagName.toLowerCase()}${id}${className}>`;
113
- }
114
- // Array handling
115
- if (Array.isArray(value)) {
116
- const arr = value.slice(0, this.maxArrayLength).map(item => this.sanitizeRecursive(item, depth + 1, seen));
117
- if (value.length > this.maxArrayLength) {
118
- arr.push(`[... ${value.length - this.maxArrayLength} more items]`);
119
- }
120
- return arr;
121
- }
122
- // Map handling
123
- if (value instanceof Map) {
124
- const entries = {};
125
- for (const [k, v] of value.entries()) {
126
- const keyStr = String(k);
127
- entries[keyStr] = this.isSensitiveKey(keyStr)
128
- ? MASK_VALUE
129
- : this.sanitizeRecursive(v, depth + 1, seen);
130
- }
131
- return entries;
132
- }
133
- // Set handling
134
- if (value instanceof Set) {
135
- return Array.from(value).map(item => this.sanitizeRecursive(item, depth + 1, seen));
136
- }
137
- // Plain Object handling
138
- const result = {};
139
- const obj = value;
140
- for (const [key, val] of Object.entries(obj)) {
141
- if (this.isSensitiveKey(key)) {
142
- result[key] = MASK_VALUE;
143
- }
144
- else {
145
- result[key] = this.sanitizeRecursive(val, depth + 1, seen);
146
- }
147
- }
148
- return result;
149
- }
150
- return String(value);
72
+ const type = typeof value;
73
+ if (type === "string" || type === "number" || type === "boolean") {
74
+ return value;
75
+ }
76
+ if (type === "bigint") {
77
+ return value.toString() + "n";
151
78
  }
79
+ if (type === "symbol") {
80
+ return value.toString();
81
+ }
82
+ if (type === "function") {
83
+ return `[Function: ${value.name || "anonymous"}]`;
84
+ }
85
+ if (typeof value === "object") {
86
+ if (seen.has(value)) {
87
+ return "[Circular Reference]";
88
+ }
89
+ seen.add(value);
90
+ if (value instanceof Error) {
91
+ return {
92
+ name: value.name,
93
+ message: value.message,
94
+ stack: value.stack,
95
+ ...value
96
+ };
97
+ }
98
+ if (value instanceof Date) {
99
+ return value.toISOString();
100
+ }
101
+ if (value instanceof RegExp) {
102
+ return value.toString();
103
+ }
104
+ if (typeof Element !== "undefined" && value instanceof Element) {
105
+ const id = value.id ? `#${value.id}` : "";
106
+ const className = value.className ? `.${String(value.className).trim().replace(/\s+/g, ".")}` : "";
107
+ return `<${value.tagName.toLowerCase()}${id}${className}>`;
108
+ }
109
+ if (Array.isArray(value)) {
110
+ const arr = value.slice(0, this.maxArrayLength).map(
111
+ (item) => this.sanitizeRecursive(item, depth + 1, seen)
112
+ );
113
+ if (value.length > this.maxArrayLength) {
114
+ arr.push(`[... ${value.length - this.maxArrayLength} more items]`);
115
+ }
116
+ return arr;
117
+ }
118
+ if (value instanceof Map) {
119
+ const entries = {};
120
+ for (const [k, v] of value.entries()) {
121
+ const keyStr = String(k);
122
+ entries[keyStr] = this.isSensitiveKey(keyStr) ? MASK_VALUE : this.sanitizeRecursive(v, depth + 1, seen);
123
+ }
124
+ return entries;
125
+ }
126
+ if (value instanceof Set) {
127
+ return Array.from(value).map(
128
+ (item) => this.sanitizeRecursive(item, depth + 1, seen)
129
+ );
130
+ }
131
+ const result = {};
132
+ const obj = value;
133
+ for (const [key, val] of Object.entries(obj)) {
134
+ if (this.isSensitiveKey(key)) {
135
+ result[key] = MASK_VALUE;
136
+ } else {
137
+ result[key] = this.sanitizeRecursive(val, depth + 1, seen);
138
+ }
139
+ }
140
+ return result;
141
+ }
142
+ return String(value);
143
+ }
152
144
  }
153
145
  const defaultSanitizer = new DataSanitizer();
154
146
  function sanitize(value, options) {
155
- if (!options) {
156
- return defaultSanitizer.sanitize(value);
157
- }
158
- return new DataSanitizer(options).sanitize(value);
147
+ if (!options) {
148
+ return defaultSanitizer.sanitize(value);
149
+ }
150
+ return new DataSanitizer(options).sanitize(value);
159
151
  }
160
152
 
161
153
  class CapDebugStore {
162
- buffer = [];
163
- maxEvents;
164
- paused = false;
165
- listeners = new Set();
166
- rafId = null;
167
- customMaskKeys = [];
168
- filterState = {
169
- tab: 'all',
170
- search: '',
171
- level: 'all',
172
- source: 'all',
173
- };
174
- constructor(maxEvents = 500, customMaskKeys = []) {
175
- this.maxEvents = Math.max(1, maxEvents);
176
- this.customMaskKeys = customMaskKeys;
177
- }
178
- setMaxEvents(max) {
179
- this.maxEvents = Math.max(1, max);
180
- if (this.buffer.length > this.maxEvents) {
181
- this.buffer = this.buffer.slice(this.buffer.length - this.maxEvents);
182
- this.notify();
183
- }
184
- }
185
- setCustomMaskKeys(keys) {
186
- this.customMaskKeys = keys;
187
- }
188
- add(event) {
189
- if (this.paused) {
190
- return null;
191
- }
192
- const sanitizedData = event.data !== undefined
193
- ? sanitize(event.data, { customMaskKeys: this.customMaskKeys })
194
- : undefined;
195
- const fullEvent = {
196
- id: event.id || this.generateId(),
197
- timestamp: event.timestamp || Date.now(),
198
- source: event.source || 'web',
199
- type: event.type,
200
- level: event.level || 'info',
201
- message: event.message,
202
- data: sanitizedData,
203
- duration: event.duration,
204
- platform: event.platform,
205
- };
206
- if (this.buffer.length >= this.maxEvents) {
207
- this.buffer.shift(); // Evict oldest FIFO
208
- }
209
- this.buffer.push(fullEvent);
210
- this.notify();
211
- return fullEvent;
212
- }
213
- clear() {
214
- this.buffer = [];
215
- this.notify();
216
- }
217
- pause() {
218
- this.paused = true;
219
- this.notify();
220
- }
221
- resume() {
222
- this.paused = false;
223
- this.notify();
224
- }
225
- togglePause() {
226
- this.paused = !this.paused;
227
- this.notify();
228
- return this.paused;
229
- }
230
- isPaused() {
231
- return this.paused;
232
- }
233
- setFilter(partial) {
234
- this.filterState = { ...this.filterState, ...partial };
235
- this.notify();
236
- }
237
- getFilter() {
238
- return this.filterState;
239
- }
240
- getAll() {
241
- return [...this.buffer];
242
- }
243
- getCounts() {
244
- const counts = {
245
- all: this.buffer.length,
246
- console: 0,
247
- errors: 0,
248
- events: 0,
249
- native: 0,
250
- device: 0,
251
- network: 0,
252
- bridge: 0,
253
- };
254
- for (const ev of this.buffer) {
255
- if (ev.source === 'console')
256
- counts.console++;
257
- if (ev.level === 'error')
258
- counts.errors++;
259
- if (ev.source === 'web' || ev.source === 'system')
260
- counts.events++;
261
- if (ev.source === 'native')
262
- counts.native++;
263
- if (ev.source === 'network')
264
- counts.network++;
265
- if (ev.source === 'bridge')
266
- counts.bridge++;
267
- }
268
- return counts;
269
- }
270
- getFiltered() {
271
- const { tab, search, level, source } = this.filterState;
272
- const query = search.trim().toLowerCase();
273
- return this.buffer.filter(event => {
274
- // 1. Tab filtering
275
- if (tab === 'console' && event.source !== 'console')
276
- return false;
277
- if (tab === 'errors' && event.level !== 'error')
278
- return false;
279
- if (tab === 'events' && (event.source !== 'web' && event.source !== 'system'))
280
- return false;
281
- if (tab === 'native' && event.source !== 'native')
282
- return false;
283
- if (tab === 'network' && event.source !== 'network')
284
- return false;
285
- if (tab === 'bridge' && event.source !== 'bridge')
286
- return false;
287
- // 2. Level filter
288
- if (level !== 'all' && event.level !== level)
289
- return false;
290
- // 3. Source filter
291
- if (source !== 'all' && event.source !== source)
292
- return false;
293
- // 4. Search query
294
- if (query) {
295
- const typeMatch = event.type.toLowerCase().includes(query);
296
- const msgMatch = event.message ? event.message.toLowerCase().includes(query) : false;
297
- const sourceMatch = event.source.toLowerCase().includes(query);
298
- if (typeMatch || msgMatch || sourceMatch)
299
- return true;
300
- if (event.data) {
301
- try {
302
- const dataStr = typeof event.data === 'string'
303
- ? event.data.toLowerCase()
304
- : JSON.stringify(event.data).toLowerCase();
305
- return dataStr.includes(query);
306
- }
307
- catch {
308
- return false;
309
- }
310
- }
311
- return false;
312
- }
313
- return true;
314
- });
315
- }
316
- exportJson() {
317
- return JSON.stringify({
318
- exportedAt: new Date().toISOString(),
319
- totalEvents: this.buffer.length,
320
- events: this.buffer,
321
- }, null, 2);
322
- }
323
- subscribe(listener) {
324
- this.listeners.add(listener);
325
- listener(this.getFiltered(), this);
326
- return () => {
327
- this.listeners.delete(listener);
328
- };
154
+ buffer = [];
155
+ maxEvents;
156
+ paused = false;
157
+ listeners = /* @__PURE__ */ new Set();
158
+ rafId = null;
159
+ customMaskKeys = [];
160
+ filterState = {
161
+ tab: "all",
162
+ search: "",
163
+ level: "all",
164
+ source: "all"
165
+ };
166
+ constructor(maxEvents = 500, customMaskKeys = []) {
167
+ this.maxEvents = Math.max(1, maxEvents);
168
+ this.customMaskKeys = customMaskKeys;
169
+ }
170
+ setMaxEvents(max) {
171
+ this.maxEvents = Math.max(1, max);
172
+ if (this.buffer.length > this.maxEvents) {
173
+ this.buffer = this.buffer.slice(this.buffer.length - this.maxEvents);
174
+ this.notify();
329
175
  }
330
- notify() {
331
- if (typeof window !== 'undefined' && typeof window.requestAnimationFrame === 'function') {
332
- if (this.rafId !== null) {
333
- cancelAnimationFrame(this.rafId);
334
- }
335
- this.rafId = requestAnimationFrame(() => {
336
- this.rafId = null;
337
- this.dispatchToListeners();
338
- });
339
- }
340
- else {
341
- this.dispatchToListeners();
342
- }
176
+ }
177
+ setCustomMaskKeys(keys) {
178
+ this.customMaskKeys = keys;
179
+ }
180
+ add(event) {
181
+ if (this.paused) {
182
+ return null;
183
+ }
184
+ const sanitizedData = event.data !== void 0 ? sanitize(event.data, { customMaskKeys: this.customMaskKeys }) : void 0;
185
+ const fullEvent = {
186
+ id: event.id || this.generateId(),
187
+ timestamp: event.timestamp || Date.now(),
188
+ source: event.source || "web",
189
+ type: event.type,
190
+ level: event.level || "info",
191
+ message: event.message,
192
+ data: sanitizedData,
193
+ duration: event.duration,
194
+ platform: event.platform
195
+ };
196
+ if (this.buffer.length >= this.maxEvents) {
197
+ this.buffer.shift();
343
198
  }
344
- dispatchToListeners() {
345
- const filtered = this.getFiltered();
346
- for (const listener of this.listeners) {
347
- try {
348
- listener(filtered, this);
349
- }
350
- catch (err) {
351
- // Prevent subscriber errors from halting store
352
- }
199
+ this.buffer.push(fullEvent);
200
+ this.notify();
201
+ return fullEvent;
202
+ }
203
+ clear() {
204
+ this.buffer = [];
205
+ this.notify();
206
+ }
207
+ pause() {
208
+ this.paused = true;
209
+ this.notify();
210
+ }
211
+ resume() {
212
+ this.paused = false;
213
+ this.notify();
214
+ }
215
+ togglePause() {
216
+ this.paused = !this.paused;
217
+ this.notify();
218
+ return this.paused;
219
+ }
220
+ isPaused() {
221
+ return this.paused;
222
+ }
223
+ setFilter(partial) {
224
+ this.filterState = { ...this.filterState, ...partial };
225
+ this.notify();
226
+ }
227
+ getFilter() {
228
+ return this.filterState;
229
+ }
230
+ getAll() {
231
+ return [...this.buffer];
232
+ }
233
+ getCounts() {
234
+ const counts = {
235
+ all: this.buffer.length,
236
+ console: 0,
237
+ errors: 0,
238
+ events: 0,
239
+ native: 0,
240
+ device: 0,
241
+ network: 0,
242
+ bridge: 0
243
+ };
244
+ for (const ev of this.buffer) {
245
+ if (ev.source === "console") counts.console++;
246
+ if (ev.level === "error") counts.errors++;
247
+ if (ev.source === "web" || ev.source === "system") counts.events++;
248
+ if (ev.source === "native") counts.native++;
249
+ if (ev.source === "network") counts.network++;
250
+ if (ev.source === "bridge") counts.bridge++;
251
+ }
252
+ return counts;
253
+ }
254
+ getFiltered() {
255
+ const { tab, search, level, source } = this.filterState;
256
+ const query = search.trim().toLowerCase();
257
+ return this.buffer.filter((event) => {
258
+ if (tab === "console" && event.source !== "console") return false;
259
+ if (tab === "errors" && event.level !== "error") return false;
260
+ if (tab === "events" && (event.source !== "web" && event.source !== "system")) return false;
261
+ if (tab === "native" && event.source !== "native") return false;
262
+ if (tab === "network" && event.source !== "network") return false;
263
+ if (tab === "bridge" && event.source !== "bridge") return false;
264
+ if (level !== "all" && event.level !== level) return false;
265
+ if (source !== "all" && event.source !== source) return false;
266
+ if (query) {
267
+ const typeMatch = event.type.toLowerCase().includes(query);
268
+ const msgMatch = event.message ? event.message.toLowerCase().includes(query) : false;
269
+ const sourceMatch = event.source.toLowerCase().includes(query);
270
+ if (typeMatch || msgMatch || sourceMatch) return true;
271
+ if (event.data) {
272
+ try {
273
+ const dataStr = typeof event.data === "string" ? event.data.toLowerCase() : JSON.stringify(event.data).toLowerCase();
274
+ return dataStr.includes(query);
275
+ } catch {
276
+ return false;
277
+ }
353
278
  }
279
+ return false;
280
+ }
281
+ return true;
282
+ });
283
+ }
284
+ exportJson() {
285
+ return JSON.stringify(
286
+ {
287
+ exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
288
+ totalEvents: this.buffer.length,
289
+ events: this.buffer
290
+ },
291
+ null,
292
+ 2
293
+ );
294
+ }
295
+ subscribe(listener) {
296
+ this.listeners.add(listener);
297
+ listener(this.getFiltered(), this);
298
+ return () => {
299
+ this.listeners.delete(listener);
300
+ };
301
+ }
302
+ notify() {
303
+ if (typeof window !== "undefined" && typeof window.requestAnimationFrame === "function") {
304
+ if (this.rafId !== null) {
305
+ cancelAnimationFrame(this.rafId);
306
+ }
307
+ this.rafId = requestAnimationFrame(() => {
308
+ this.rafId = null;
309
+ this.dispatchToListeners();
310
+ });
311
+ } else {
312
+ this.dispatchToListeners();
354
313
  }
355
- generateId() {
356
- return 'ev_' + Math.random().toString(36).substring(2, 9) + '_' + Date.now().toString(36);
314
+ }
315
+ dispatchToListeners() {
316
+ const filtered = this.getFiltered();
317
+ for (const listener of this.listeners) {
318
+ try {
319
+ listener(filtered, this);
320
+ } catch (err) {
321
+ }
357
322
  }
323
+ }
324
+ generateId() {
325
+ return "ev_" + Math.random().toString(36).substring(2, 9) + "_" + Date.now().toString(36);
326
+ }
358
327
  }
359
328
 
360
329
  class ConsoleCollector {
361
- id = 'console';
362
- running = false;
363
- originalMethods = {};
364
- start() {
365
- if (this.running || typeof console === 'undefined')
366
- return;
367
- const methods = [
368
- { method: 'log', level: 'info' },
369
- { method: 'info', level: 'info' },
370
- { method: 'warn', level: 'warn' },
371
- { method: 'error', level: 'error' },
372
- { method: 'debug', level: 'debug' },
373
- ];
374
- for (const { method, level } of methods) {
375
- const original = console[method]?.bind(console);
376
- this.originalMethods[method] = original;
377
- console[method] = (...args) => {
378
- // 1. Call original console method
379
- if (original) {
380
- try {
381
- original(...args);
382
- }
383
- catch {
384
- // ignore
385
- }
386
- }
387
- // 2. Dispatch CapDebug event safely
388
- try {
389
- this.emitConsoleEvent(method, level, args);
390
- }
391
- catch {
392
- // ignore to prevent crashing host application
393
- }
394
- };
395
- }
396
- this.running = true;
397
- }
398
- stop() {
399
- if (!this.running || typeof console === 'undefined')
400
- return;
401
- for (const method of Object.keys(this.originalMethods)) {
402
- const original = this.originalMethods[method];
403
- if (original) {
404
- console[method] = original;
405
- }
330
+ id = "console";
331
+ running = false;
332
+ originalMethods = {};
333
+ start() {
334
+ if (this.running || typeof console === "undefined") return;
335
+ const methods = [
336
+ { method: "log", level: "info" },
337
+ { method: "info", level: "info" },
338
+ { method: "warn", level: "warn" },
339
+ { method: "error", level: "error" },
340
+ { method: "debug", level: "debug" }
341
+ ];
342
+ for (const { method, level } of methods) {
343
+ const original = console[method]?.bind(console);
344
+ this.originalMethods[method] = original;
345
+ console[method] = (...args) => {
346
+ if (original) {
347
+ try {
348
+ original(...args);
349
+ } catch {
350
+ }
406
351
  }
407
- this.originalMethods = {};
408
- this.running = false;
409
- }
410
- emitConsoleEvent(method, level, args) {
411
- if (typeof window === 'undefined')
412
- return;
413
- let message = '';
414
- let data = undefined;
415
- if (args.length === 1) {
416
- const arg = args[0];
417
- if (typeof arg === 'string' || typeof arg === 'number' || typeof arg === 'boolean') {
418
- message = String(arg);
419
- }
420
- else if (arg instanceof Error) {
421
- message = `${arg.name}: ${arg.message}`;
422
- data = { name: arg.name, message: arg.message, stack: arg.stack };
423
- }
424
- else {
425
- message = `[${typeof arg}]`;
426
- data = arg;
427
- }
428
- }
429
- else if (args.length > 1) {
430
- // Multiple arguments
431
- const first = args[0];
432
- if (typeof first === 'string') {
433
- message = first;
434
- data = args.slice(1);
435
- }
436
- else {
437
- message = args.map(a => (typeof a === 'object' && a !== null ? '[Object]' : String(a))).join(' ');
438
- data = args;
439
- }
352
+ try {
353
+ this.emitConsoleEvent(method, level, args);
354
+ } catch {
440
355
  }
441
- window.dispatchEvent(new CustomEvent('capdebug', {
442
- detail: {
443
- source: 'console',
444
- type: `console.${method}`,
445
- level,
446
- message,
447
- data,
448
- timestamp: Date.now(),
449
- },
450
- }));
356
+ };
451
357
  }
358
+ this.running = true;
359
+ }
360
+ stop() {
361
+ if (!this.running || typeof console === "undefined") return;
362
+ for (const method of Object.keys(this.originalMethods)) {
363
+ const original = this.originalMethods[method];
364
+ if (original) {
365
+ console[method] = original;
366
+ }
367
+ }
368
+ this.originalMethods = {};
369
+ this.running = false;
370
+ }
371
+ emitConsoleEvent(method, level, args) {
372
+ if (typeof window === "undefined") return;
373
+ let message = "";
374
+ let data = void 0;
375
+ if (args.length === 1) {
376
+ const arg = args[0];
377
+ if (typeof arg === "string" || typeof arg === "number" || typeof arg === "boolean") {
378
+ message = String(arg);
379
+ } else if (arg instanceof Error) {
380
+ message = `${arg.name}: ${arg.message}`;
381
+ data = { name: arg.name, message: arg.message, stack: arg.stack };
382
+ } else {
383
+ message = `[${typeof arg}]`;
384
+ data = arg;
385
+ }
386
+ } else if (args.length > 1) {
387
+ const first = args[0];
388
+ if (typeof first === "string") {
389
+ message = first;
390
+ data = args.slice(1);
391
+ } else {
392
+ message = args.map((a) => typeof a === "object" && a !== null ? "[Object]" : String(a)).join(" ");
393
+ data = args;
394
+ }
395
+ }
396
+ window.dispatchEvent(
397
+ new CustomEvent("capdebug", {
398
+ detail: {
399
+ source: "console",
400
+ type: `console.${method}`,
401
+ level,
402
+ message,
403
+ data,
404
+ timestamp: Date.now()
405
+ }
406
+ })
407
+ );
408
+ }
452
409
  }
453
410
 
454
411
  class ErrorCollector {
455
- id = 'error';
456
- running = false;
457
- errorHandler = null;
458
- rejectionHandler = null;
459
- start() {
460
- if (this.running || typeof window === 'undefined')
461
- return;
462
- this.errorHandler = (event) => {
463
- try {
464
- const error = event.error;
465
- const message = event.message || (error && error.message) || 'Uncaught Error';
466
- const stack = error && error.stack ? error.stack : undefined;
467
- window.dispatchEvent(new CustomEvent('capdebug', {
468
- detail: {
469
- source: 'web',
470
- type: 'window.error',
471
- level: 'error',
472
- message,
473
- data: {
474
- filename: event.filename,
475
- lineno: event.lineno,
476
- colno: event.colno,
477
- stack,
478
- errorName: error?.name,
479
- },
480
- timestamp: Date.now(),
481
- },
482
- }));
483
- }
484
- catch {
485
- // ignore
486
- }
487
- };
488
- this.rejectionHandler = (event) => {
489
- try {
490
- const reason = event.reason;
491
- let message = 'Unhandled Promise Rejection';
492
- let stack = undefined;
493
- let data = reason;
494
- if (reason instanceof Error) {
495
- message = `Unhandled Rejection: ${reason.message}`;
496
- stack = reason.stack;
497
- data = {
498
- name: reason.name,
499
- message: reason.message,
500
- stack,
501
- };
502
- }
503
- else if (typeof reason === 'string') {
504
- message = `Unhandled Rejection: ${reason}`;
505
- }
506
- window.dispatchEvent(new CustomEvent('capdebug', {
507
- detail: {
508
- source: 'web',
509
- type: 'unhandledrejection',
510
- level: 'error',
511
- message,
512
- data: {
513
- reason: data,
514
- stack,
515
- },
516
- timestamp: Date.now(),
517
- },
518
- }));
412
+ id = "error";
413
+ running = false;
414
+ errorHandler = null;
415
+ rejectionHandler = null;
416
+ start() {
417
+ if (this.running || typeof window === "undefined") return;
418
+ this.errorHandler = (event) => {
419
+ try {
420
+ const error = event.error;
421
+ const message = event.message || error && error.message || "Uncaught Error";
422
+ const stack = error && error.stack ? error.stack : void 0;
423
+ window.dispatchEvent(
424
+ new CustomEvent("capdebug", {
425
+ detail: {
426
+ source: "web",
427
+ type: "window.error",
428
+ level: "error",
429
+ message,
430
+ data: {
431
+ filename: event.filename,
432
+ lineno: event.lineno,
433
+ colno: event.colno,
434
+ stack,
435
+ errorName: error?.name
436
+ },
437
+ timestamp: Date.now()
519
438
  }
520
- catch {
521
- // ignore
439
+ })
440
+ );
441
+ } catch {
442
+ }
443
+ };
444
+ this.rejectionHandler = (event) => {
445
+ try {
446
+ const reason = event.reason;
447
+ let message = "Unhandled Promise Rejection";
448
+ let stack = void 0;
449
+ let data = reason;
450
+ if (reason instanceof Error) {
451
+ message = `Unhandled Rejection: ${reason.message}`;
452
+ stack = reason.stack;
453
+ data = {
454
+ name: reason.name,
455
+ message: reason.message,
456
+ stack
457
+ };
458
+ } else if (typeof reason === "string") {
459
+ message = `Unhandled Rejection: ${reason}`;
460
+ }
461
+ window.dispatchEvent(
462
+ new CustomEvent("capdebug", {
463
+ detail: {
464
+ source: "web",
465
+ type: "unhandledrejection",
466
+ level: "error",
467
+ message,
468
+ data: {
469
+ reason: data,
470
+ stack
471
+ },
472
+ timestamp: Date.now()
522
473
  }
523
- };
524
- window.addEventListener('error', this.errorHandler);
525
- window.addEventListener('unhandledrejection', this.rejectionHandler);
526
- this.running = true;
527
- }
528
- stop() {
529
- if (!this.running || typeof window === 'undefined')
530
- return;
531
- if (this.errorHandler) {
532
- window.removeEventListener('error', this.errorHandler);
533
- this.errorHandler = null;
534
- }
535
- if (this.rejectionHandler) {
536
- window.removeEventListener('unhandledrejection', this.rejectionHandler);
537
- this.rejectionHandler = null;
538
- }
539
- this.running = false;
474
+ })
475
+ );
476
+ } catch {
477
+ }
478
+ };
479
+ window.addEventListener("error", this.errorHandler);
480
+ window.addEventListener("unhandledrejection", this.rejectionHandler);
481
+ this.running = true;
482
+ }
483
+ stop() {
484
+ if (!this.running || typeof window === "undefined") return;
485
+ if (this.errorHandler) {
486
+ window.removeEventListener("error", this.errorHandler);
487
+ this.errorHandler = null;
488
+ }
489
+ if (this.rejectionHandler) {
490
+ window.removeEventListener("unhandledrejection", this.rejectionHandler);
491
+ this.rejectionHandler = null;
540
492
  }
493
+ this.running = false;
494
+ }
541
495
  }
542
496
 
543
497
  class NetworkCollector {
544
- id = 'network';
545
- running = false;
546
- originalFetch = null;
547
- options;
548
- constructor(options = {}) {
549
- this.options = {
550
- body: false,
551
- ...options,
552
- };
553
- }
554
- setOptions(options) {
555
- this.options = { ...this.options, ...options };
556
- }
557
- start() {
558
- if (this.running || typeof window === 'undefined' || !window.fetch)
559
- return;
560
- this.originalFetch = window.fetch;
561
- const self = this;
562
- window.fetch = async function (input, init) {
563
- const startTime = performance.now();
564
- const timestamp = Date.now();
565
- let url = '';
566
- let method = 'GET';
567
- if (typeof input === 'string') {
568
- url = input;
569
- }
570
- else if (input instanceof URL) {
571
- url = input.toString();
572
- }
573
- else if (typeof Request !== 'undefined' && input instanceof Request) {
574
- url = input.url;
575
- method = input.method;
576
- }
577
- if (init && init.method) {
578
- method = init.method.toUpperCase();
579
- }
580
- let requestHeaders = {};
581
- if (init && init.headers) {
582
- if (typeof Headers !== 'undefined' && init.headers instanceof Headers) {
583
- init.headers.forEach((value, key) => {
584
- requestHeaders[key] = value;
585
- });
586
- }
587
- else if (Array.isArray(init.headers)) {
588
- for (const [k, v] of init.headers) {
589
- requestHeaders[k] = v;
590
- }
591
- }
592
- else if (typeof init.headers === 'object') {
593
- requestHeaders = { ...init.headers };
594
- }
595
- }
596
- let requestBody = undefined;
597
- if (self.options.body && init && init.body) {
598
- try {
599
- if (typeof init.body === 'string') {
600
- try {
601
- requestBody = JSON.parse(init.body);
602
- }
603
- catch {
604
- requestBody = init.body;
605
- }
606
- }
607
- else {
608
- requestBody = '[Binary/Stream Body]';
609
- }
610
- }
611
- catch {
612
- requestBody = '[Unreadable Body]';
613
- }
614
- }
498
+ id = "network";
499
+ running = false;
500
+ originalFetch = null;
501
+ options;
502
+ constructor(options = {}) {
503
+ this.options = {
504
+ body: false,
505
+ ...options
506
+ };
507
+ }
508
+ setOptions(options) {
509
+ this.options = { ...this.options, ...options };
510
+ }
511
+ start() {
512
+ if (this.running || typeof window === "undefined" || !window.fetch) return;
513
+ this.originalFetch = window.fetch;
514
+ const self = this;
515
+ window.fetch = async function(input, init) {
516
+ const startTime = performance.now();
517
+ const timestamp = Date.now();
518
+ let url = "";
519
+ let method = "GET";
520
+ if (typeof input === "string") {
521
+ url = input;
522
+ } else if (input instanceof URL) {
523
+ url = input.toString();
524
+ } else if (typeof Request !== "undefined" && input instanceof Request) {
525
+ url = input.url;
526
+ method = input.method;
527
+ }
528
+ if (init && init.method) {
529
+ method = init.method.toUpperCase();
530
+ }
531
+ let requestHeaders = {};
532
+ if (init && init.headers) {
533
+ if (typeof Headers !== "undefined" && init.headers instanceof Headers) {
534
+ init.headers.forEach((value, key) => {
535
+ requestHeaders[key] = value;
536
+ });
537
+ } else if (Array.isArray(init.headers)) {
538
+ for (const [k, v] of init.headers) {
539
+ requestHeaders[k] = v;
540
+ }
541
+ } else if (typeof init.headers === "object") {
542
+ requestHeaders = { ...init.headers };
543
+ }
544
+ }
545
+ let requestBody = void 0;
546
+ if (self.options.body && init && init.body) {
547
+ try {
548
+ if (typeof init.body === "string") {
615
549
  try {
616
- const response = await self.originalFetch.call(this, input, init);
617
- const duration = Math.round(performance.now() - startTime);
618
- let level = 'info';
619
- if (response.status >= 500) {
620
- level = 'error';
621
- }
622
- else if (response.status >= 400) {
623
- level = 'warn';
624
- }
625
- let responseBody = undefined;
626
- if (self.options.body) {
627
- try {
628
- const clone = response.clone();
629
- const contentType = clone.headers.get('content-type') || '';
630
- if (contentType.includes('application/json')) {
631
- responseBody = await clone.json();
632
- }
633
- else if (contentType.includes('text/')) {
634
- responseBody = await clone.text();
635
- }
636
- }
637
- catch {
638
- responseBody = '[Could not clone response body]';
639
- }
640
- }
641
- const responseHeaders = {};
642
- response.headers.forEach((v, k) => {
643
- responseHeaders[k] = v;
644
- });
645
- self.emitNetworkEvent({
646
- type: `fetch ${method}`,
647
- level,
648
- message: `${method} ${url} → ${response.status} ${response.statusText} (${duration}ms)`,
649
- duration,
650
- timestamp,
651
- data: {
652
- method,
653
- url,
654
- status: response.status,
655
- statusText: response.statusText,
656
- ok: response.ok,
657
- duration,
658
- requestHeaders,
659
- responseHeaders,
660
- requestBody,
661
- responseBody,
662
- },
663
- });
664
- return response;
550
+ requestBody = JSON.parse(init.body);
551
+ } catch {
552
+ requestBody = init.body;
665
553
  }
666
- catch (err) {
667
- const duration = Math.round(performance.now() - startTime);
668
- self.emitNetworkEvent({
669
- type: `fetch ${method}`,
670
- level: 'error',
671
- message: `${method} ${url} → Failed: ${err?.message || 'Network Error'} (${duration}ms)`,
672
- duration,
673
- timestamp,
674
- data: {
675
- method,
676
- url,
677
- error: err?.message || String(err),
678
- duration,
679
- requestHeaders,
680
- requestBody,
681
- },
682
- });
683
- throw err;
554
+ } else {
555
+ requestBody = "[Binary/Stream Body]";
556
+ }
557
+ } catch {
558
+ requestBody = "[Unreadable Body]";
559
+ }
560
+ }
561
+ try {
562
+ const response = await self.originalFetch.call(this, input, init);
563
+ const duration = Math.round(performance.now() - startTime);
564
+ let level = "info";
565
+ if (response.status >= 500) {
566
+ level = "error";
567
+ } else if (response.status >= 400) {
568
+ level = "warn";
569
+ }
570
+ let responseBody = void 0;
571
+ if (self.options.body) {
572
+ try {
573
+ const clone = response.clone();
574
+ const contentType = clone.headers.get("content-type") || "";
575
+ if (contentType.includes("application/json")) {
576
+ responseBody = await clone.json();
577
+ } else if (contentType.includes("text/")) {
578
+ responseBody = await clone.text();
684
579
  }
685
- };
686
- this.running = true;
687
- }
688
- stop() {
689
- if (!this.running || typeof window === 'undefined')
690
- return;
691
- if (this.originalFetch) {
692
- window.fetch = this.originalFetch;
693
- this.originalFetch = null;
580
+ } catch {
581
+ responseBody = "[Could not clone response body]";
582
+ }
694
583
  }
695
- this.running = false;
584
+ const responseHeaders = {};
585
+ response.headers.forEach((v, k) => {
586
+ responseHeaders[k] = v;
587
+ });
588
+ self.emitNetworkEvent({
589
+ type: `fetch ${method}`,
590
+ level,
591
+ message: `${method} ${url} \u2192 ${response.status} ${response.statusText} (${duration}ms)`,
592
+ duration,
593
+ timestamp,
594
+ data: {
595
+ method,
596
+ url,
597
+ status: response.status,
598
+ statusText: response.statusText,
599
+ ok: response.ok,
600
+ duration,
601
+ requestHeaders,
602
+ responseHeaders,
603
+ requestBody,
604
+ responseBody
605
+ }
606
+ });
607
+ return response;
608
+ } catch (err) {
609
+ const duration = Math.round(performance.now() - startTime);
610
+ self.emitNetworkEvent({
611
+ type: `fetch ${method}`,
612
+ level: "error",
613
+ message: `${method} ${url} \u2192 Failed: ${err?.message || "Network Error"} (${duration}ms)`,
614
+ duration,
615
+ timestamp,
616
+ data: {
617
+ method,
618
+ url,
619
+ error: err?.message || String(err),
620
+ duration,
621
+ requestHeaders,
622
+ requestBody
623
+ }
624
+ });
625
+ throw err;
626
+ }
627
+ };
628
+ this.running = true;
629
+ }
630
+ stop() {
631
+ if (!this.running || typeof window === "undefined") return;
632
+ if (this.originalFetch) {
633
+ window.fetch = this.originalFetch;
634
+ this.originalFetch = null;
696
635
  }
697
- emitNetworkEvent(detail) {
698
- if (typeof window === 'undefined')
699
- return;
700
- try {
701
- window.dispatchEvent(new CustomEvent('capdebug', {
702
- detail: {
703
- source: 'network',
704
- ...detail,
705
- },
706
- }));
707
- }
708
- catch {
709
- // ignore
710
- }
636
+ this.running = false;
637
+ }
638
+ emitNetworkEvent(detail) {
639
+ if (typeof window === "undefined") return;
640
+ try {
641
+ window.dispatchEvent(
642
+ new CustomEvent("capdebug", {
643
+ detail: {
644
+ source: "network",
645
+ ...detail
646
+ }
647
+ })
648
+ );
649
+ } catch {
711
650
  }
651
+ }
712
652
  }
713
653
 
714
654
  class CustomEventCollector {
715
- id = 'custom-event';
716
- store;
717
- running = false;
718
- handler = null;
719
- constructor(store) {
720
- this.store = store;
721
- }
722
- start() {
723
- if (this.running || typeof window === 'undefined')
724
- return;
725
- this.handler = (event) => {
726
- const customEvent = event;
727
- if (customEvent && customEvent.detail) {
728
- const detail = customEvent.detail;
729
- this.store.add({
730
- id: detail.id,
731
- timestamp: detail.timestamp || Date.now(),
732
- source: detail.source || 'web',
733
- type: detail.type || 'custom-event',
734
- level: detail.level || 'info',
735
- message: detail.message,
736
- data: detail.data,
737
- duration: detail.duration,
738
- platform: detail.platform,
739
- });
740
- }
741
- };
742
- window.addEventListener('capdebug', this.handler);
743
- this.running = true;
744
- }
745
- stop() {
746
- if (!this.running || typeof window === 'undefined')
747
- return;
748
- if (this.handler) {
749
- window.removeEventListener('capdebug', this.handler);
750
- this.handler = null;
751
- }
752
- this.running = false;
655
+ id = "custom-event";
656
+ store;
657
+ running = false;
658
+ handler = null;
659
+ constructor(store) {
660
+ this.store = store;
661
+ }
662
+ start() {
663
+ if (this.running || typeof window === "undefined") return;
664
+ this.handler = (event) => {
665
+ const customEvent = event;
666
+ if (customEvent && customEvent.detail) {
667
+ const detail = customEvent.detail;
668
+ this.store.add({
669
+ id: detail.id,
670
+ timestamp: detail.timestamp || Date.now(),
671
+ source: detail.source || "web",
672
+ type: detail.type || "custom-event",
673
+ level: detail.level || "info",
674
+ message: detail.message,
675
+ data: detail.data,
676
+ duration: detail.duration,
677
+ platform: detail.platform
678
+ });
679
+ }
680
+ };
681
+ window.addEventListener("capdebug", this.handler);
682
+ this.running = true;
683
+ }
684
+ stop() {
685
+ if (!this.running || typeof window === "undefined") return;
686
+ if (this.handler) {
687
+ window.removeEventListener("capdebug", this.handler);
688
+ this.handler = null;
753
689
  }
690
+ this.running = false;
691
+ }
754
692
  }
755
693
 
756
694
  class BrowserEventCollector {
757
- id = 'browser-event';
758
- running = false;
759
- observedEvents = new Map();
760
- start() {
761
- this.running = true;
762
- }
763
- observe(eventName) {
764
- if (!this.running || typeof window === 'undefined' || this.observedEvents.has(eventName))
765
- return;
766
- const listener = (event) => {
767
- let data = {
768
- type: event.type,
769
- timeStamp: event.timeStamp,
770
- };
771
- if (event.type === 'visibilitychange' && typeof document !== 'undefined') {
772
- data.visibilityState = document.visibilityState;
773
- data.hidden = document.hidden;
774
- }
775
- else if (event.type === 'online' || event.type === 'offline') {
776
- data.onLine = typeof navigator !== 'undefined' ? navigator.onLine : undefined;
777
- }
778
- window.dispatchEvent(new CustomEvent('capdebug', {
779
- detail: {
780
- source: 'system',
781
- type: `event.${eventName}`,
782
- level: 'info',
783
- message: `Browser event fired: ${eventName}`,
784
- data,
785
- timestamp: Date.now(),
786
- },
787
- }));
788
- };
789
- const target = eventName === 'visibilitychange' && typeof document !== 'undefined'
790
- ? document
791
- : window;
792
- target.addEventListener(eventName, listener);
793
- this.observedEvents.set(eventName, listener);
794
- }
795
- unobserve(eventName) {
796
- const listener = this.observedEvents.get(eventName);
797
- if (!listener)
798
- return;
799
- const target = eventName === 'visibilitychange' && typeof document !== 'undefined'
800
- ? document
801
- : window;
802
- target.removeEventListener(eventName, listener);
803
- this.observedEvents.delete(eventName);
804
- }
805
- stop() {
806
- for (const [eventName, listener] of this.observedEvents.entries()) {
807
- const target = eventName === 'visibilitychange' && typeof document !== 'undefined'
808
- ? document
809
- : window;
810
- target.removeEventListener(eventName, listener);
811
- }
812
- this.observedEvents.clear();
813
- this.running = false;
695
+ id = "browser-event";
696
+ running = false;
697
+ observedEvents = /* @__PURE__ */ new Map();
698
+ start() {
699
+ this.running = true;
700
+ }
701
+ observe(eventName) {
702
+ if (!this.running || typeof window === "undefined" || this.observedEvents.has(eventName)) return;
703
+ const listener = (event) => {
704
+ let data = {
705
+ type: event.type,
706
+ timeStamp: event.timeStamp
707
+ };
708
+ if (event.type === "visibilitychange" && typeof document !== "undefined") {
709
+ data.visibilityState = document.visibilityState;
710
+ data.hidden = document.hidden;
711
+ } else if (event.type === "online" || event.type === "offline") {
712
+ data.onLine = typeof navigator !== "undefined" ? navigator.onLine : void 0;
713
+ }
714
+ window.dispatchEvent(
715
+ new CustomEvent("capdebug", {
716
+ detail: {
717
+ source: "system",
718
+ type: `event.${eventName}`,
719
+ level: "info",
720
+ message: `Browser event fired: ${eventName}`,
721
+ data,
722
+ timestamp: Date.now()
723
+ }
724
+ })
725
+ );
726
+ };
727
+ const target = eventName === "visibilitychange" && typeof document !== "undefined" ? document : window;
728
+ target.addEventListener(eventName, listener);
729
+ this.observedEvents.set(eventName, listener);
730
+ }
731
+ unobserve(eventName) {
732
+ const listener = this.observedEvents.get(eventName);
733
+ if (!listener) return;
734
+ const target = eventName === "visibilitychange" && typeof document !== "undefined" ? document : window;
735
+ target.removeEventListener(eventName, listener);
736
+ this.observedEvents.delete(eventName);
737
+ }
738
+ stop() {
739
+ for (const [eventName, listener] of this.observedEvents.entries()) {
740
+ const target = eventName === "visibilitychange" && typeof document !== "undefined" ? document : window;
741
+ target.removeEventListener(eventName, listener);
814
742
  }
743
+ this.observedEvents.clear();
744
+ this.running = false;
745
+ }
815
746
  }
816
747
 
817
748
  class BridgeCollector {
818
- id = 'bridge';
819
- running = false;
820
- start() {
821
- if (this.running)
822
- return;
823
- // Reserved for non-intrusive Capacitor bridge telemetry
824
- this.running = true;
825
- }
826
- stop() {
827
- this.running = false;
828
- }
829
- emitBridgeCall(pluginName, methodName, options, duration) {
830
- if (!this.running || typeof window === 'undefined')
831
- return;
832
- window.dispatchEvent(new CustomEvent('capdebug', {
833
- detail: {
834
- source: 'bridge',
835
- type: `${pluginName}.${methodName}`,
836
- level: 'debug',
837
- message: `Bridge call: ${pluginName}.${methodName}`,
838
- data: options,
839
- duration,
840
- timestamp: Date.now(),
841
- },
842
- }));
843
- }
749
+ id = "bridge";
750
+ running = false;
751
+ start() {
752
+ if (this.running) return;
753
+ this.running = true;
754
+ }
755
+ stop() {
756
+ this.running = false;
757
+ }
758
+ emitBridgeCall(pluginName, methodName, options, duration) {
759
+ if (!this.running || typeof window === "undefined") return;
760
+ window.dispatchEvent(
761
+ new CustomEvent("capdebug", {
762
+ detail: {
763
+ source: "bridge",
764
+ type: `${pluginName}.${methodName}`,
765
+ level: "debug",
766
+ message: `Bridge call: ${pluginName}.${methodName}`,
767
+ data: options,
768
+ duration,
769
+ timestamp: Date.now()
770
+ }
771
+ })
772
+ );
773
+ }
844
774
  }
845
775
 
846
776
  const VIEWER_STYLES = `
@@ -1567,1203 +1497,1992 @@ const VIEWER_STYLES = `
1567
1497
  }
1568
1498
  `;
1569
1499
 
1570
- const STORAGE_KEY = 'capdebug_btn_pos';
1500
+ const STORAGE_KEY = "capdebug_btn_pos";
1571
1501
  class FloatingButton {
1572
- element;
1573
- badgeElement;
1574
- options;
1575
- isDragging = false;
1576
- hasMoved = false;
1577
- startX = 0;
1578
- startY = 0;
1579
- initialLeft = 0;
1580
- initialTop = 0;
1581
- constructor(options = {}) {
1582
- this.options = {
1583
- position: 'right',
1584
- persistPosition: true,
1585
- ...options,
1586
- };
1587
- this.element = document.createElement('div');
1588
- this.element.className = 'cd-floating-btn';
1589
- this.element.setAttribute('role', 'button');
1590
- this.element.setAttribute('aria-label', 'Open CapDebug');
1591
- const logo = document.createElement('span');
1592
- logo.className = 'cd-btn-logo';
1593
- logo.innerHTML = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="margin-right: 4px"><path d="m8 2 1.88 1.88"/><path d="M14.12 3.88 16 2"/><path d="M9 7.13v-1a3.003 3.003 0 1 1 6 0v1"/><path d="M12 20c-3.3 0-6-2.7-6-6v-3a4 4 0 0 1 4-4h4a4 4 0 0 1 4 4v3c0 3.3-2.7 6-6 6"/><path d="M12 20v-9"/><path d="M6.53 9C4.6 8.8 3 7.1 3 5"/><path d="M6 13H2"/><path d="M3 21c0-2.1 1.7-3.9 3.8-4"/><path d="M20.97 5c0 2.1-1.6 3.8-3.5 4"/><path d="M22 13h-4"/><path d="M17.2 17c2.1.1 3.8 1.9 3.8 4"/></svg>CAP`;
1594
- this.badgeElement = document.createElement('span');
1595
- this.badgeElement.className = 'cd-btn-badge';
1596
- this.badgeElement.textContent = '0';
1597
- this.element.appendChild(logo);
1598
- this.element.appendChild(this.badgeElement);
1599
- this.initPosition();
1600
- this.bindEvents();
1601
- }
1602
- getElement() {
1603
- return this.element;
1604
- }
1605
- setErrorCount(count) {
1606
- if (count > 0) {
1607
- this.badgeElement.textContent = count > 99 ? '99+' : String(count);
1608
- this.badgeElement.classList.add('visible');
1609
- }
1610
- else {
1611
- this.badgeElement.classList.remove('visible');
1612
- }
1613
- }
1614
- show() {
1615
- this.element.style.display = 'flex';
1616
- }
1617
- hide() {
1618
- this.element.style.display = 'none';
1502
+ element;
1503
+ badgeElement;
1504
+ options;
1505
+ isDragging = false;
1506
+ hasMoved = false;
1507
+ startX = 0;
1508
+ startY = 0;
1509
+ initialLeft = 0;
1510
+ initialTop = 0;
1511
+ constructor(options = {}) {
1512
+ this.options = {
1513
+ position: "right",
1514
+ persistPosition: true,
1515
+ ...options
1516
+ };
1517
+ this.element = document.createElement("div");
1518
+ this.element.className = "cd-floating-btn";
1519
+ this.element.setAttribute("role", "button");
1520
+ this.element.setAttribute("aria-label", "Open CapDebug");
1521
+ const logo = document.createElement("span");
1522
+ logo.className = "cd-btn-logo";
1523
+ logo.innerHTML = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="margin-right: 4px"><path d="m8 2 1.88 1.88"/><path d="M14.12 3.88 16 2"/><path d="M9 7.13v-1a3.003 3.003 0 1 1 6 0v1"/><path d="M12 20c-3.3 0-6-2.7-6-6v-3a4 4 0 0 1 4-4h4a4 4 0 0 1 4 4v3c0 3.3-2.7 6-6 6"/><path d="M12 20v-9"/><path d="M6.53 9C4.6 8.8 3 7.1 3 5"/><path d="M6 13H2"/><path d="M3 21c0-2.1 1.7-3.9 3.8-4"/><path d="M20.97 5c0 2.1-1.6 3.8-3.5 4"/><path d="M22 13h-4"/><path d="M17.2 17c2.1.1 3.8 1.9 3.8 4"/></svg>CAP`;
1524
+ this.badgeElement = document.createElement("span");
1525
+ this.badgeElement.className = "cd-btn-badge";
1526
+ this.badgeElement.textContent = "0";
1527
+ this.element.appendChild(logo);
1528
+ this.element.appendChild(this.badgeElement);
1529
+ this.initPosition();
1530
+ this.bindEvents();
1531
+ }
1532
+ getElement() {
1533
+ return this.element;
1534
+ }
1535
+ setErrorCount(count) {
1536
+ if (count > 0) {
1537
+ this.badgeElement.textContent = count > 99 ? "99+" : String(count);
1538
+ this.badgeElement.classList.add("visible");
1539
+ } else {
1540
+ this.badgeElement.classList.remove("visible");
1619
1541
  }
1620
- initPosition() {
1621
- let savedPos = null;
1622
- if (this.options.persistPosition && typeof localStorage !== 'undefined') {
1623
- try {
1624
- const stored = localStorage.getItem(STORAGE_KEY);
1625
- if (stored) {
1626
- savedPos = JSON.parse(stored);
1627
- }
1628
- }
1629
- catch {
1630
- // ignore
1631
- }
1632
- }
1633
- const windowWidth = typeof window !== 'undefined' ? window.innerWidth : 400;
1634
- const windowHeight = typeof window !== 'undefined' ? window.innerHeight : 800;
1635
- let x = windowWidth - 76;
1636
- let y = Math.max(80, windowHeight * 0.4);
1637
- if (savedPos && typeof savedPos.x === 'number' && typeof savedPos.y === 'number') {
1638
- x = Math.min(Math.max(10, savedPos.x), windowWidth - 76);
1639
- y = Math.min(Math.max(60, savedPos.y), windowHeight - 60);
1640
- }
1641
- else if (typeof this.options.position === 'object') {
1642
- x = this.options.position.x;
1643
- y = this.options.position.y;
1644
- }
1645
- else if (this.options.position === 'left') {
1646
- x = 12;
1647
- }
1648
- this.setPosition(x, y);
1649
- }
1650
- setPosition(x, y) {
1651
- this.element.style.left = `${x}px`;
1652
- this.element.style.top = `${y}px`;
1653
- this.element.style.right = 'auto';
1654
- this.element.style.bottom = 'auto';
1655
- }
1656
- bindEvents() {
1657
- const onPointerDown = (e) => {
1658
- this.isDragging = true;
1659
- this.hasMoved = false;
1660
- this.startX = e.clientX;
1661
- this.startY = e.clientY;
1662
- const rect = this.element.getBoundingClientRect();
1663
- this.initialLeft = rect.left;
1664
- this.initialTop = rect.top;
1665
- this.element.classList.add('dragging');
1666
- this.element.setPointerCapture(e.pointerId);
1667
- };
1668
- const onPointerMove = (e) => {
1669
- if (!this.isDragging)
1670
- return;
1671
- const deltaX = e.clientX - this.startX;
1672
- const deltaY = e.clientY - this.startY;
1673
- if (Math.abs(deltaX) > 4 || Math.abs(deltaY) > 4) {
1674
- this.hasMoved = true;
1675
- }
1676
- let newX = this.initialLeft + deltaX;
1677
- let newY = this.initialTop + deltaY;
1678
- const maxX = window.innerWidth - this.element.offsetWidth - 8;
1679
- const maxY = window.innerHeight - this.element.offsetHeight - 8;
1680
- newX = Math.max(8, Math.min(newX, maxX));
1681
- newY = Math.max(40, Math.min(newY, maxY));
1682
- this.setPosition(newX, newY);
1683
- };
1684
- const onPointerUp = (e) => {
1685
- if (!this.isDragging)
1686
- return;
1687
- this.isDragging = false;
1688
- this.element.classList.remove('dragging');
1689
- try {
1690
- this.element.releasePointerCapture(e.pointerId);
1691
- }
1692
- catch {
1693
- // ignore
1694
- }
1695
- if (!this.hasMoved) {
1696
- // Tap / Click action
1697
- if (this.options.onClick) {
1698
- this.options.onClick();
1699
- }
1700
- }
1701
- else {
1702
- // Snap to nearest edge (left or right)
1703
- this.snapToEdge();
1704
- }
1705
- };
1706
- this.element.addEventListener('pointerdown', onPointerDown);
1707
- this.element.addEventListener('pointermove', onPointerMove);
1708
- this.element.addEventListener('pointerup', onPointerUp);
1709
- this.element.addEventListener('pointercancel', onPointerUp);
1710
- }
1711
- snapToEdge() {
1712
- const rect = this.element.getBoundingClientRect();
1713
- const windowWidth = window.innerWidth;
1714
- const windowHeight = window.innerHeight;
1715
- const snapLeft = 12;
1716
- const snapRight = windowWidth - rect.width - 12;
1717
- const targetX = rect.left + rect.width / 2 < windowWidth / 2 ? snapLeft : snapRight;
1718
- const targetY = Math.max(50, Math.min(rect.top, windowHeight - rect.height - 50));
1719
- this.element.style.transition = 'left 0.2s cubic-bezier(0.16, 1, 0.3, 1), top 0.2s cubic-bezier(0.16, 1, 0.3, 1)';
1720
- this.setPosition(targetX, targetY);
1721
- setTimeout(() => {
1722
- this.element.style.transition = '';
1723
- }, 200);
1724
- if (this.options.persistPosition && typeof localStorage !== 'undefined') {
1725
- try {
1726
- localStorage.setItem(STORAGE_KEY, JSON.stringify({ x: targetX, y: targetY }));
1727
- }
1728
- catch {
1729
- // ignore
1730
- }
1731
- }
1542
+ }
1543
+ show() {
1544
+ this.element.style.display = "flex";
1545
+ }
1546
+ hide() {
1547
+ this.element.style.display = "none";
1548
+ }
1549
+ initPosition() {
1550
+ let savedPos = null;
1551
+ if (this.options.persistPosition && typeof localStorage !== "undefined") {
1552
+ try {
1553
+ const stored = localStorage.getItem(STORAGE_KEY);
1554
+ if (stored) {
1555
+ savedPos = JSON.parse(stored);
1556
+ }
1557
+ } catch {
1558
+ }
1559
+ }
1560
+ const windowWidth = typeof window !== "undefined" ? window.innerWidth : 400;
1561
+ const windowHeight = typeof window !== "undefined" ? window.innerHeight : 800;
1562
+ let x = windowWidth - 76;
1563
+ let y = Math.max(80, windowHeight * 0.4);
1564
+ if (savedPos && typeof savedPos.x === "number" && typeof savedPos.y === "number") {
1565
+ x = Math.min(Math.max(10, savedPos.x), windowWidth - 76);
1566
+ y = Math.min(Math.max(60, savedPos.y), windowHeight - 60);
1567
+ } else if (typeof this.options.position === "object") {
1568
+ x = this.options.position.x;
1569
+ y = this.options.position.y;
1570
+ } else if (this.options.position === "left") {
1571
+ x = 12;
1572
+ }
1573
+ this.setPosition(x, y);
1574
+ }
1575
+ setPosition(x, y) {
1576
+ this.element.style.left = `${x}px`;
1577
+ this.element.style.top = `${y}px`;
1578
+ this.element.style.right = "auto";
1579
+ this.element.style.bottom = "auto";
1580
+ }
1581
+ bindEvents() {
1582
+ const onPointerDown = (e) => {
1583
+ this.isDragging = true;
1584
+ this.hasMoved = false;
1585
+ this.startX = e.clientX;
1586
+ this.startY = e.clientY;
1587
+ const rect = this.element.getBoundingClientRect();
1588
+ this.initialLeft = rect.left;
1589
+ this.initialTop = rect.top;
1590
+ this.element.classList.add("dragging");
1591
+ this.element.setPointerCapture(e.pointerId);
1592
+ };
1593
+ const onPointerMove = (e) => {
1594
+ if (!this.isDragging) return;
1595
+ const deltaX = e.clientX - this.startX;
1596
+ const deltaY = e.clientY - this.startY;
1597
+ if (Math.abs(deltaX) > 4 || Math.abs(deltaY) > 4) {
1598
+ this.hasMoved = true;
1599
+ }
1600
+ let newX = this.initialLeft + deltaX;
1601
+ let newY = this.initialTop + deltaY;
1602
+ const maxX = window.innerWidth - this.element.offsetWidth - 8;
1603
+ const maxY = window.innerHeight - this.element.offsetHeight - 8;
1604
+ newX = Math.max(8, Math.min(newX, maxX));
1605
+ newY = Math.max(40, Math.min(newY, maxY));
1606
+ this.setPosition(newX, newY);
1607
+ };
1608
+ const onPointerUp = (e) => {
1609
+ if (!this.isDragging) return;
1610
+ this.isDragging = false;
1611
+ this.element.classList.remove("dragging");
1612
+ try {
1613
+ this.element.releasePointerCapture(e.pointerId);
1614
+ } catch {
1615
+ }
1616
+ if (!this.hasMoved) {
1617
+ if (this.options.onClick) {
1618
+ this.options.onClick();
1619
+ }
1620
+ } else {
1621
+ this.snapToEdge();
1622
+ }
1623
+ };
1624
+ this.element.addEventListener("pointerdown", onPointerDown);
1625
+ this.element.addEventListener("pointermove", onPointerMove);
1626
+ this.element.addEventListener("pointerup", onPointerUp);
1627
+ this.element.addEventListener("pointercancel", onPointerUp);
1628
+ }
1629
+ snapToEdge() {
1630
+ const rect = this.element.getBoundingClientRect();
1631
+ const windowWidth = window.innerWidth;
1632
+ const windowHeight = window.innerHeight;
1633
+ const snapLeft = 12;
1634
+ const snapRight = windowWidth - rect.width - 12;
1635
+ const targetX = rect.left + rect.width / 2 < windowWidth / 2 ? snapLeft : snapRight;
1636
+ const targetY = Math.max(50, Math.min(rect.top, windowHeight - rect.height - 50));
1637
+ this.element.style.transition = "left 0.2s cubic-bezier(0.16, 1, 0.3, 1), top 0.2s cubic-bezier(0.16, 1, 0.3, 1)";
1638
+ this.setPosition(targetX, targetY);
1639
+ setTimeout(() => {
1640
+ this.element.style.transition = "";
1641
+ }, 200);
1642
+ if (this.options.persistPosition && typeof localStorage !== "undefined") {
1643
+ try {
1644
+ localStorage.setItem(STORAGE_KEY, JSON.stringify({ x: targetX, y: targetY }));
1645
+ } catch {
1646
+ }
1732
1647
  }
1648
+ }
1733
1649
  }
1734
1650
 
1735
1651
  class JsonViewer {
1736
- static render(data, initialExpandedDepth = 2) {
1737
- const container = document.createElement('div');
1738
- container.className = 'cd-json-tree';
1739
- if (data === undefined) {
1740
- const undefinedSpan = document.createElement('span');
1741
- undefinedSpan.className = 'cd-json-null';
1742
- undefinedSpan.textContent = 'undefined';
1743
- container.appendChild(undefinedSpan);
1744
- return container;
1745
- }
1746
- const rootNode = JsonViewer.renderNode('', data, 0, initialExpandedDepth);
1747
- container.appendChild(rootNode);
1748
- return container;
1749
- }
1750
- static renderNode(key, value, depth, maxExpandedDepth) {
1751
- const node = document.createElement('div');
1752
- node.className = 'cd-json-node';
1753
- const row = document.createElement('div');
1754
- row.className = 'cd-json-row';
1755
- const isObject = typeof value === 'object' && value !== null;
1756
- const isArray = Array.isArray(value);
1757
- // Toggle button for objects/arrays
1758
- if (isObject) {
1759
- const toggle = document.createElement('span');
1760
- toggle.className = 'cd-json-toggle';
1761
- toggle.innerHTML = '▼';
1762
- row.appendChild(toggle);
1763
- }
1764
- else {
1765
- const spacer = document.createElement('span');
1766
- spacer.style.width = '14px';
1767
- spacer.style.display = 'inline-block';
1768
- row.appendChild(spacer);
1769
- }
1770
- // Key (if present)
1771
- if (key !== '') {
1772
- const keySpan = document.createElement('span');
1773
- keySpan.className = 'cd-json-key';
1774
- keySpan.textContent = typeof key === 'number' ? `[${key}]` : `"${key}"`;
1775
- row.appendChild(keySpan);
1776
- const colonSpan = document.createElement('span');
1777
- colonSpan.className = 'cd-json-colon';
1778
- colonSpan.textContent = ': ';
1779
- row.appendChild(colonSpan);
1780
- }
1781
- // Value preview or primitive
1782
- if (isObject) {
1783
- const entries = isArray
1784
- ? value.map((v, i) => [i, v])
1785
- : Object.entries(value);
1786
- const bracketOpen = isArray ? '[' : '{';
1787
- const bracketClose = isArray ? ']' : '}';
1788
- const previewSpan = document.createElement('span');
1789
- previewSpan.className = 'cd-json-colon';
1790
- previewSpan.textContent = `${bracketOpen} ${entries.length} ${isArray ? 'items' : 'keys'} ${bracketClose}`;
1791
- row.appendChild(previewSpan);
1792
- // Copy object JSON button
1793
- const copyBtn = document.createElement('button');
1794
- copyBtn.className = 'cd-json-copy-btn';
1795
- copyBtn.textContent = 'copy';
1796
- copyBtn.title = 'Copy branch JSON';
1797
- copyBtn.addEventListener('click', e => {
1798
- e.stopPropagation();
1799
- JsonViewer.copyToClipboard(JSON.stringify(value, null, 2), copyBtn);
1800
- });
1801
- row.appendChild(copyBtn);
1802
- node.appendChild(row);
1803
- // Children container
1804
- const childrenContainer = document.createElement('div');
1805
- childrenContainer.className = 'cd-json-children';
1806
- const isInitiallyExpanded = depth < maxExpandedDepth;
1807
- if (!isInitiallyExpanded) {
1808
- childrenContainer.classList.add('hidden');
1809
- const toggle = row.querySelector('.cd-json-toggle');
1810
- if (toggle)
1811
- toggle.classList.add('collapsed');
1812
- }
1813
- for (const [childKey, childVal] of entries) {
1814
- childrenContainer.appendChild(JsonViewer.renderNode(childKey, childVal, depth + 1, maxExpandedDepth));
1815
- }
1816
- node.appendChild(childrenContainer);
1817
- // Toggle collapse on row or toggle click
1818
- row.addEventListener('click', e => {
1819
- if (e.target.tagName.toLowerCase() === 'button')
1820
- return;
1821
- const toggle = row.querySelector('.cd-json-toggle');
1822
- const isHidden = childrenContainer.classList.toggle('hidden');
1823
- if (toggle) {
1824
- toggle.classList.toggle('collapsed', isHidden);
1825
- }
1826
- });
1827
- }
1828
- else {
1829
- // Primitive value
1830
- const valSpan = document.createElement('span');
1831
- const valType = typeof value;
1832
- if (value === null) {
1833
- valSpan.className = 'cd-json-null';
1834
- valSpan.textContent = 'null';
1835
- }
1836
- else if (valType === 'string') {
1837
- valSpan.className = 'cd-json-string';
1838
- valSpan.textContent = `"${value}"`;
1839
- }
1840
- else if (valType === 'number') {
1841
- valSpan.className = 'cd-json-number';
1842
- valSpan.textContent = String(value);
1843
- }
1844
- else if (valType === 'boolean') {
1845
- valSpan.className = 'cd-json-boolean';
1846
- valSpan.textContent = String(value);
1847
- }
1848
- else {
1849
- valSpan.className = 'cd-json-null';
1850
- valSpan.textContent = String(value);
1851
- }
1852
- row.appendChild(valSpan);
1853
- // Copy primitive value button
1854
- const copyBtn = document.createElement('button');
1855
- copyBtn.className = 'cd-json-copy-btn';
1856
- copyBtn.textContent = 'copy';
1857
- copyBtn.title = 'Copy value';
1858
- copyBtn.addEventListener('click', e => {
1859
- e.stopPropagation();
1860
- JsonViewer.copyToClipboard(String(value), copyBtn);
1861
- });
1862
- row.appendChild(copyBtn);
1863
- node.appendChild(row);
1864
- }
1865
- return node;
1866
- }
1867
- static async copyToClipboard(text, triggerBtn) {
1868
- try {
1869
- if (navigator.clipboard && window.isSecureContext) {
1870
- await navigator.clipboard.writeText(text);
1871
- }
1872
- else {
1873
- const textarea = document.createElement('textarea');
1874
- textarea.value = text;
1875
- textarea.style.position = 'fixed';
1876
- textarea.style.left = '-9999px';
1877
- textarea.style.top = '-9999px';
1878
- document.body.appendChild(textarea);
1879
- textarea.focus();
1880
- textarea.select();
1881
- document.execCommand('copy');
1882
- document.body.removeChild(textarea);
1883
- }
1884
- if (triggerBtn) {
1885
- const originalText = triggerBtn.textContent;
1886
- triggerBtn.textContent = 'copied!';
1887
- triggerBtn.style.color = '#34d399';
1888
- setTimeout(() => {
1889
- triggerBtn.textContent = originalText;
1890
- triggerBtn.style.color = '';
1891
- }, 1500);
1892
- }
1893
- return true;
1894
- }
1895
- catch {
1896
- return false;
1897
- }
1652
+ static render(data, initialExpandedDepth = 2) {
1653
+ const container = document.createElement("div");
1654
+ container.className = "cd-json-tree";
1655
+ if (data === void 0) {
1656
+ const undefinedSpan = document.createElement("span");
1657
+ undefinedSpan.className = "cd-json-null";
1658
+ undefinedSpan.textContent = "undefined";
1659
+ container.appendChild(undefinedSpan);
1660
+ return container;
1661
+ }
1662
+ const rootNode = JsonViewer.renderNode("", data, 0, initialExpandedDepth);
1663
+ container.appendChild(rootNode);
1664
+ return container;
1665
+ }
1666
+ static renderNode(key, value, depth, maxExpandedDepth) {
1667
+ const node = document.createElement("div");
1668
+ node.className = "cd-json-node";
1669
+ const row = document.createElement("div");
1670
+ row.className = "cd-json-row";
1671
+ const isObject = typeof value === "object" && value !== null;
1672
+ const isArray = Array.isArray(value);
1673
+ if (isObject) {
1674
+ const toggle = document.createElement("span");
1675
+ toggle.className = "cd-json-toggle";
1676
+ toggle.innerHTML = "\u25BC";
1677
+ row.appendChild(toggle);
1678
+ } else {
1679
+ const spacer = document.createElement("span");
1680
+ spacer.style.width = "14px";
1681
+ spacer.style.display = "inline-block";
1682
+ row.appendChild(spacer);
1683
+ }
1684
+ if (key !== "") {
1685
+ const keySpan = document.createElement("span");
1686
+ keySpan.className = "cd-json-key";
1687
+ keySpan.textContent = typeof key === "number" ? `[${key}]` : `"${key}"`;
1688
+ row.appendChild(keySpan);
1689
+ const colonSpan = document.createElement("span");
1690
+ colonSpan.className = "cd-json-colon";
1691
+ colonSpan.textContent = ": ";
1692
+ row.appendChild(colonSpan);
1693
+ }
1694
+ if (isObject) {
1695
+ const entries = isArray ? value.map((v, i) => [i, v]) : Object.entries(value);
1696
+ const bracketOpen = isArray ? "[" : "{";
1697
+ const bracketClose = isArray ? "]" : "}";
1698
+ const previewSpan = document.createElement("span");
1699
+ previewSpan.className = "cd-json-colon";
1700
+ previewSpan.textContent = `${bracketOpen} ${entries.length} ${isArray ? "items" : "keys"} ${bracketClose}`;
1701
+ row.appendChild(previewSpan);
1702
+ const copyBtn = document.createElement("button");
1703
+ copyBtn.className = "cd-json-copy-btn";
1704
+ copyBtn.textContent = "copy";
1705
+ copyBtn.title = "Copy branch JSON";
1706
+ copyBtn.addEventListener("click", (e) => {
1707
+ e.stopPropagation();
1708
+ JsonViewer.copyToClipboard(JSON.stringify(value, null, 2), copyBtn);
1709
+ });
1710
+ row.appendChild(copyBtn);
1711
+ node.appendChild(row);
1712
+ const childrenContainer = document.createElement("div");
1713
+ childrenContainer.className = "cd-json-children";
1714
+ const isInitiallyExpanded = depth < maxExpandedDepth;
1715
+ if (!isInitiallyExpanded) {
1716
+ childrenContainer.classList.add("hidden");
1717
+ const toggle = row.querySelector(".cd-json-toggle");
1718
+ if (toggle) toggle.classList.add("collapsed");
1719
+ }
1720
+ for (const [childKey, childVal] of entries) {
1721
+ childrenContainer.appendChild(
1722
+ JsonViewer.renderNode(childKey, childVal, depth + 1, maxExpandedDepth)
1723
+ );
1724
+ }
1725
+ node.appendChild(childrenContainer);
1726
+ row.addEventListener("click", (e) => {
1727
+ if (e.target.tagName.toLowerCase() === "button") return;
1728
+ const toggle = row.querySelector(".cd-json-toggle");
1729
+ const isHidden = childrenContainer.classList.toggle("hidden");
1730
+ if (toggle) {
1731
+ toggle.classList.toggle("collapsed", isHidden);
1732
+ }
1733
+ });
1734
+ } else {
1735
+ const valSpan = document.createElement("span");
1736
+ const valType = typeof value;
1737
+ if (value === null) {
1738
+ valSpan.className = "cd-json-null";
1739
+ valSpan.textContent = "null";
1740
+ } else if (valType === "string") {
1741
+ valSpan.className = "cd-json-string";
1742
+ valSpan.textContent = `"${value}"`;
1743
+ } else if (valType === "number") {
1744
+ valSpan.className = "cd-json-number";
1745
+ valSpan.textContent = String(value);
1746
+ } else if (valType === "boolean") {
1747
+ valSpan.className = "cd-json-boolean";
1748
+ valSpan.textContent = String(value);
1749
+ } else {
1750
+ valSpan.className = "cd-json-null";
1751
+ valSpan.textContent = String(value);
1752
+ }
1753
+ row.appendChild(valSpan);
1754
+ const copyBtn = document.createElement("button");
1755
+ copyBtn.className = "cd-json-copy-btn";
1756
+ copyBtn.textContent = "copy";
1757
+ copyBtn.title = "Copy value";
1758
+ copyBtn.addEventListener("click", (e) => {
1759
+ e.stopPropagation();
1760
+ JsonViewer.copyToClipboard(String(value), copyBtn);
1761
+ });
1762
+ row.appendChild(copyBtn);
1763
+ node.appendChild(row);
1764
+ }
1765
+ return node;
1766
+ }
1767
+ static async copyToClipboard(text, triggerBtn) {
1768
+ try {
1769
+ if (navigator.clipboard && window.isSecureContext) {
1770
+ await navigator.clipboard.writeText(text);
1771
+ } else {
1772
+ const textarea = document.createElement("textarea");
1773
+ textarea.value = text;
1774
+ textarea.style.position = "fixed";
1775
+ textarea.style.left = "-9999px";
1776
+ textarea.style.top = "-9999px";
1777
+ document.body.appendChild(textarea);
1778
+ textarea.focus();
1779
+ textarea.select();
1780
+ document.execCommand("copy");
1781
+ document.body.removeChild(textarea);
1782
+ }
1783
+ if (triggerBtn) {
1784
+ const originalText = triggerBtn.textContent;
1785
+ triggerBtn.textContent = "copied!";
1786
+ triggerBtn.style.color = "#34d399";
1787
+ setTimeout(() => {
1788
+ triggerBtn.textContent = originalText;
1789
+ triggerBtn.style.color = "";
1790
+ }, 1500);
1791
+ }
1792
+ return true;
1793
+ } catch {
1794
+ return false;
1898
1795
  }
1796
+ }
1899
1797
  }
1900
1798
 
1901
- const CapDebugNative = core.registerPlugin('CapDebug', {
1902
- web: () => Promise.resolve().then(function () { return web; }).then(m => new m.CapDebugWeb()),
1799
+ const CapDebugNative = core.registerPlugin("CapDebug", {
1800
+ web: () => Promise.resolve().then(function () { return web; }).then((m) => new m.CapDebugWeb())
1903
1801
  });
1904
1802
 
1905
1803
  class DebugPanel {
1906
- element;
1907
- backdrop;
1908
- store;
1909
- options;
1910
- isOpen = false;
1911
- // Header UI
1912
- statusIndicator;
1913
- titleCountSpan;
1914
- pauseBtn;
1915
- // Tab elements
1916
- tabElements = new Map();
1917
- tabBadgeElements = new Map();
1918
- // Search & Filter UI
1919
- searchInput;
1920
- levelPills = new Map();
1921
- // Content
1922
- contentContainer;
1923
- eventListContainer;
1924
- deviceViewContainer;
1925
- // Detail Overlay
1926
- detailOverlay;
1927
- selectedEvent = null;
1928
- // Device tab state
1929
- isDeviceLoading = false;
1930
- deviceData = null;
1931
- constructor(store, options = {}) {
1932
- this.store = store;
1933
- this.options = {
1934
- defaultTab: 'all',
1935
- ...options,
1936
- };
1937
- this.backdrop = document.createElement('div');
1938
- this.backdrop.className = 'cd-backdrop';
1939
- this.element = document.createElement('div');
1940
- this.element.className = 'cd-panel';
1941
- this.buildUI();
1942
- this.bindStore();
1943
- if (this.options.defaultTab) {
1944
- this.store.setFilter({ tab: this.options.defaultTab });
1945
- }
1946
- }
1947
- getElement() {
1948
- return this.element;
1949
- }
1950
- getBackdrop() {
1951
- return this.backdrop;
1804
+ element;
1805
+ backdrop;
1806
+ store;
1807
+ options;
1808
+ isOpen = false;
1809
+ // Header UI
1810
+ statusIndicator;
1811
+ titleCountSpan;
1812
+ pauseBtn;
1813
+ // Tab elements
1814
+ tabElements = /* @__PURE__ */ new Map();
1815
+ tabBadgeElements = /* @__PURE__ */ new Map();
1816
+ // Search & Filter UI
1817
+ searchInput;
1818
+ levelPills = /* @__PURE__ */ new Map();
1819
+ // Content
1820
+ contentContainer;
1821
+ eventListContainer;
1822
+ deviceViewContainer;
1823
+ // Detail Overlay
1824
+ detailOverlay;
1825
+ selectedEvent = null;
1826
+ // Device tab state
1827
+ isDeviceLoading = false;
1828
+ deviceData = null;
1829
+ constructor(store, options = {}) {
1830
+ this.store = store;
1831
+ this.options = {
1832
+ defaultTab: "all",
1833
+ ...options
1834
+ };
1835
+ this.backdrop = document.createElement("div");
1836
+ this.backdrop.className = "cd-backdrop";
1837
+ this.element = document.createElement("div");
1838
+ this.element.className = "cd-panel";
1839
+ this.buildUI();
1840
+ this.bindStore();
1841
+ if (this.options.defaultTab) {
1842
+ this.store.setFilter({ tab: this.options.defaultTab });
1952
1843
  }
1953
- open() {
1954
- this.isOpen = true;
1955
- this.element.classList.add('open');
1956
- this.backdrop.classList.add('open');
1957
- // If device tab is active, trigger lazy load
1958
- if (this.store.getFilter().tab === 'device') {
1959
- this.loadDeviceDiagnostics();
1960
- }
1844
+ }
1845
+ getElement() {
1846
+ return this.element;
1847
+ }
1848
+ getBackdrop() {
1849
+ return this.backdrop;
1850
+ }
1851
+ open() {
1852
+ this.isOpen = true;
1853
+ this.element.classList.add("open");
1854
+ this.backdrop.classList.add("open");
1855
+ if (this.store.getFilter().tab === "device") {
1856
+ this.loadDeviceDiagnostics();
1961
1857
  }
1962
- close() {
1963
- this.isOpen = false;
1964
- this.element.classList.remove('open');
1965
- this.backdrop.classList.remove('open');
1966
- this.closeDetail();
1967
- if (this.options.onClose) {
1968
- this.options.onClose();
1969
- }
1858
+ }
1859
+ close() {
1860
+ this.isOpen = false;
1861
+ this.element.classList.remove("open");
1862
+ this.backdrop.classList.remove("open");
1863
+ this.closeDetail();
1864
+ if (this.options.onClose) {
1865
+ this.options.onClose();
1970
1866
  }
1971
- toggle() {
1972
- if (this.isOpen) {
1973
- this.close();
1974
- return false;
1975
- }
1976
- else {
1977
- this.open();
1978
- return true;
1979
- }
1867
+ }
1868
+ toggle() {
1869
+ if (this.isOpen) {
1870
+ this.close();
1871
+ return false;
1872
+ } else {
1873
+ this.open();
1874
+ return true;
1980
1875
  }
1981
- isPanelOpen() {
1982
- return this.isOpen;
1983
- }
1984
- buildUI() {
1985
- this.backdrop.addEventListener('click', () => this.close());
1986
- // 1. Header
1987
- const header = document.createElement('div');
1988
- header.className = 'cd-header';
1989
- const headerLeft = document.createElement('div');
1990
- headerLeft.className = 'cd-header-left';
1991
- this.statusIndicator = document.createElement('div');
1992
- this.statusIndicator.className = 'cd-status-indicator';
1993
- this.statusIndicator.title = 'CapDebug Active';
1994
- const headerTitle = document.createElement('div');
1995
- headerTitle.className = 'cd-header-title';
1996
- headerTitle.innerHTML = `<span>CapDebug</span>`;
1997
- this.titleCountSpan = document.createElement('span');
1998
- this.titleCountSpan.style.color = 'var(--cd-text-muted)';
1999
- this.titleCountSpan.style.fontSize = '11px';
2000
- this.titleCountSpan.textContent = '(0)';
2001
- headerTitle.appendChild(this.titleCountSpan);
2002
- headerLeft.appendChild(this.statusIndicator);
2003
- headerLeft.appendChild(headerTitle);
2004
- const headerActions = document.createElement('div');
2005
- headerActions.className = 'cd-header-actions';
2006
- // Pause/Resume Button
2007
- this.pauseBtn = document.createElement('button');
2008
- this.pauseBtn.className = 'cd-btn-icon';
2009
- this.pauseBtn.title = 'Pause/Resume capture';
2010
- this.pauseBtn.innerHTML = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="6" y="4" width="4" height="16"/><rect x="14" y="4" width="4" height="16"/></svg>`;
2011
- this.pauseBtn.addEventListener('click', () => {
2012
- const isPaused = this.store.togglePause();
2013
- this.updatePauseState(isPaused);
2014
- });
2015
- // Clear Button
2016
- const clearBtn = document.createElement('button');
2017
- clearBtn.className = 'cd-btn-icon';
2018
- clearBtn.title = 'Clear events';
2019
- clearBtn.innerHTML = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="4.93" y1="4.93" x2="19.07" y2="19.07"/></svg>`;
2020
- clearBtn.addEventListener('click', () => {
2021
- this.store.clear();
2022
- this.closeDetail();
2023
- });
2024
- // Export Button
2025
- const exportBtn = document.createElement('button');
2026
- exportBtn.className = 'cd-btn-icon';
2027
- exportBtn.title = 'Export JSON to clipboard';
2028
- exportBtn.innerHTML = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>`;
2029
- exportBtn.addEventListener('click', async () => {
2030
- const json = this.store.exportJson();
2031
- await JsonViewer.copyToClipboard(json, exportBtn);
2032
- });
2033
- // Close Button
2034
- const closeBtn = document.createElement('button');
2035
- closeBtn.className = 'cd-btn-icon';
2036
- closeBtn.title = 'Close viewer';
2037
- closeBtn.innerHTML = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>`;
2038
- closeBtn.addEventListener('click', () => this.close());
2039
- headerActions.appendChild(this.pauseBtn);
2040
- headerActions.appendChild(clearBtn);
2041
- headerActions.appendChild(exportBtn);
2042
- headerActions.appendChild(closeBtn);
2043
- header.appendChild(headerLeft);
2044
- header.appendChild(headerActions);
2045
- this.element.appendChild(header);
2046
- // 2. Tabs Bar
2047
- const tabsBar = document.createElement('div');
2048
- tabsBar.className = 'cd-tabs-bar';
2049
- const tabs = [
2050
- { id: 'all', label: 'All' },
2051
- { id: 'console', label: 'Console' },
2052
- { id: 'errors', label: 'Errors' },
2053
- { id: 'events', label: 'Events' },
2054
- { id: 'native', label: 'Native' },
2055
- { id: 'device', label: 'Device' },
2056
- { id: 'network', label: 'Network' },
2057
- { id: 'bridge', label: 'Bridge' },
2058
- ];
2059
- for (const { id, label } of tabs) {
2060
- const tabBtn = document.createElement('button');
2061
- tabBtn.className = 'cd-tab';
2062
- if (id === this.options.defaultTab) {
2063
- tabBtn.classList.add('active');
2064
- }
2065
- const labelSpan = document.createElement('span');
2066
- labelSpan.textContent = label;
2067
- const badgeSpan = document.createElement('span');
2068
- badgeSpan.className = 'cd-tab-badge';
2069
- badgeSpan.textContent = '0';
2070
- tabBtn.appendChild(labelSpan);
2071
- tabBtn.appendChild(badgeSpan);
2072
- tabBtn.addEventListener('click', () => {
2073
- this.selectTab(id);
2074
- });
2075
- this.tabElements.set(id, tabBtn);
2076
- this.tabBadgeElements.set(id, badgeSpan);
2077
- tabsBar.appendChild(tabBtn);
2078
- }
2079
- this.element.appendChild(tabsBar);
2080
- // 3. Toolbar (Search & Level Filters)
2081
- const toolbar = document.createElement('div');
2082
- toolbar.className = 'cd-toolbar';
2083
- this.searchInput = document.createElement('input');
2084
- this.searchInput.className = 'cd-search-input';
2085
- this.searchInput.type = 'text';
2086
- this.searchInput.placeholder = 'Filter logs, types, payloads...';
2087
- this.searchInput.addEventListener('input', () => {
2088
- this.store.setFilter({ search: this.searchInput.value });
2089
- });
2090
- const levelFilters = document.createElement('div');
2091
- levelFilters.className = 'cd-level-filters';
2092
- const levels = ['all', 'error', 'warn', 'info', 'debug'];
2093
- for (const lvl of levels) {
2094
- const pill = document.createElement('button');
2095
- pill.className = 'cd-level-pill';
2096
- pill.setAttribute('data-level', lvl);
2097
- pill.textContent = lvl === 'all' ? 'All' : lvl.toUpperCase();
2098
- if (lvl === 'all')
2099
- pill.classList.add('active');
2100
- pill.addEventListener('click', () => {
2101
- this.selectLevel(lvl);
2102
- });
2103
- this.levelPills.set(lvl, pill);
2104
- levelFilters.appendChild(pill);
2105
- }
2106
- toolbar.appendChild(this.searchInput);
2107
- toolbar.appendChild(levelFilters);
2108
- this.element.appendChild(toolbar);
2109
- // 4. Content Area
2110
- this.contentContainer = document.createElement('div');
2111
- this.contentContainer.className = 'cd-content';
2112
- this.eventListContainer = document.createElement('div');
2113
- this.eventListContainer.className = 'cd-event-list';
2114
- this.deviceViewContainer = document.createElement('div');
2115
- this.deviceViewContainer.className = 'cd-device-view';
2116
- this.deviceViewContainer.style.display = 'none';
2117
- this.contentContainer.appendChild(this.eventListContainer);
2118
- this.contentContainer.appendChild(this.deviceViewContainer);
2119
- this.element.appendChild(this.contentContainer);
2120
- // 5. Detail Overlay
2121
- this.buildDetailOverlay();
2122
- }
2123
- selectTab(tab) {
2124
- for (const [id, el] of this.tabElements.entries()) {
2125
- el.classList.toggle('active', id === tab);
2126
- }
2127
- this.store.setFilter({ tab });
2128
- this.closeDetail();
2129
- if (tab === 'device') {
2130
- this.eventListContainer.style.display = 'none';
2131
- this.deviceViewContainer.style.display = 'flex';
2132
- this.loadDeviceDiagnostics();
2133
- }
2134
- else {
2135
- this.eventListContainer.style.display = 'flex';
2136
- this.deviceViewContainer.style.display = 'none';
2137
- }
1876
+ }
1877
+ isPanelOpen() {
1878
+ return this.isOpen;
1879
+ }
1880
+ buildUI() {
1881
+ this.backdrop.addEventListener("click", () => this.close());
1882
+ const header = document.createElement("div");
1883
+ header.className = "cd-header";
1884
+ const headerLeft = document.createElement("div");
1885
+ headerLeft.className = "cd-header-left";
1886
+ this.statusIndicator = document.createElement("div");
1887
+ this.statusIndicator.className = "cd-status-indicator";
1888
+ this.statusIndicator.title = "CapDebug Active";
1889
+ const headerTitle = document.createElement("div");
1890
+ headerTitle.className = "cd-header-title";
1891
+ headerTitle.innerHTML = `<span>CapDebug</span>`;
1892
+ this.titleCountSpan = document.createElement("span");
1893
+ this.titleCountSpan.style.color = "var(--cd-text-muted)";
1894
+ this.titleCountSpan.style.fontSize = "11px";
1895
+ this.titleCountSpan.textContent = "(0)";
1896
+ headerTitle.appendChild(this.titleCountSpan);
1897
+ headerLeft.appendChild(this.statusIndicator);
1898
+ headerLeft.appendChild(headerTitle);
1899
+ const headerActions = document.createElement("div");
1900
+ headerActions.className = "cd-header-actions";
1901
+ this.pauseBtn = document.createElement("button");
1902
+ this.pauseBtn.className = "cd-btn-icon";
1903
+ this.pauseBtn.title = "Pause/Resume capture";
1904
+ this.pauseBtn.innerHTML = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="6" y="4" width="4" height="16"/><rect x="14" y="4" width="4" height="16"/></svg>`;
1905
+ this.pauseBtn.addEventListener("click", () => {
1906
+ const isPaused = this.store.togglePause();
1907
+ this.updatePauseState(isPaused);
1908
+ });
1909
+ const clearBtn = document.createElement("button");
1910
+ clearBtn.className = "cd-btn-icon";
1911
+ clearBtn.title = "Clear events";
1912
+ clearBtn.innerHTML = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="4.93" y1="4.93" x2="19.07" y2="19.07"/></svg>`;
1913
+ clearBtn.addEventListener("click", () => {
1914
+ this.store.clear();
1915
+ this.closeDetail();
1916
+ });
1917
+ const exportBtn = document.createElement("button");
1918
+ exportBtn.className = "cd-btn-icon";
1919
+ exportBtn.title = "Export JSON to clipboard";
1920
+ exportBtn.innerHTML = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>`;
1921
+ exportBtn.addEventListener("click", async () => {
1922
+ const json = this.store.exportJson();
1923
+ await JsonViewer.copyToClipboard(json, exportBtn);
1924
+ });
1925
+ const closeBtn = document.createElement("button");
1926
+ closeBtn.className = "cd-btn-icon";
1927
+ closeBtn.title = "Close viewer";
1928
+ closeBtn.innerHTML = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>`;
1929
+ closeBtn.addEventListener("click", () => this.close());
1930
+ headerActions.appendChild(this.pauseBtn);
1931
+ headerActions.appendChild(clearBtn);
1932
+ headerActions.appendChild(exportBtn);
1933
+ headerActions.appendChild(closeBtn);
1934
+ header.appendChild(headerLeft);
1935
+ header.appendChild(headerActions);
1936
+ this.element.appendChild(header);
1937
+ const tabsBar = document.createElement("div");
1938
+ tabsBar.className = "cd-tabs-bar";
1939
+ const tabs = [
1940
+ { id: "all", label: "All" },
1941
+ { id: "console", label: "Console" },
1942
+ { id: "errors", label: "Errors" },
1943
+ { id: "events", label: "Events" },
1944
+ { id: "native", label: "Native" },
1945
+ { id: "device", label: "Device" },
1946
+ { id: "network", label: "Network" },
1947
+ { id: "bridge", label: "Bridge" }
1948
+ ];
1949
+ for (const { id, label } of tabs) {
1950
+ const tabBtn = document.createElement("button");
1951
+ tabBtn.className = "cd-tab";
1952
+ if (id === this.options.defaultTab) {
1953
+ tabBtn.classList.add("active");
1954
+ }
1955
+ const labelSpan = document.createElement("span");
1956
+ labelSpan.textContent = label;
1957
+ const badgeSpan = document.createElement("span");
1958
+ badgeSpan.className = "cd-tab-badge";
1959
+ badgeSpan.textContent = "0";
1960
+ tabBtn.appendChild(labelSpan);
1961
+ tabBtn.appendChild(badgeSpan);
1962
+ tabBtn.addEventListener("click", () => {
1963
+ this.selectTab(id);
1964
+ });
1965
+ this.tabElements.set(id, tabBtn);
1966
+ this.tabBadgeElements.set(id, badgeSpan);
1967
+ tabsBar.appendChild(tabBtn);
1968
+ }
1969
+ this.element.appendChild(tabsBar);
1970
+ const toolbar = document.createElement("div");
1971
+ toolbar.className = "cd-toolbar";
1972
+ this.searchInput = document.createElement("input");
1973
+ this.searchInput.className = "cd-search-input";
1974
+ this.searchInput.type = "text";
1975
+ this.searchInput.placeholder = "Filter logs, types, payloads...";
1976
+ this.searchInput.addEventListener("input", () => {
1977
+ this.store.setFilter({ search: this.searchInput.value });
1978
+ });
1979
+ const levelFilters = document.createElement("div");
1980
+ levelFilters.className = "cd-level-filters";
1981
+ const levels = ["all", "error", "warn", "info", "debug"];
1982
+ for (const lvl of levels) {
1983
+ const pill = document.createElement("button");
1984
+ pill.className = "cd-level-pill";
1985
+ pill.setAttribute("data-level", lvl);
1986
+ pill.textContent = lvl === "all" ? "All" : lvl.toUpperCase();
1987
+ if (lvl === "all") pill.classList.add("active");
1988
+ pill.addEventListener("click", () => {
1989
+ this.selectLevel(lvl);
1990
+ });
1991
+ this.levelPills.set(lvl, pill);
1992
+ levelFilters.appendChild(pill);
1993
+ }
1994
+ toolbar.appendChild(this.searchInput);
1995
+ toolbar.appendChild(levelFilters);
1996
+ this.element.appendChild(toolbar);
1997
+ this.contentContainer = document.createElement("div");
1998
+ this.contentContainer.className = "cd-content";
1999
+ this.eventListContainer = document.createElement("div");
2000
+ this.eventListContainer.className = "cd-event-list";
2001
+ this.deviceViewContainer = document.createElement("div");
2002
+ this.deviceViewContainer.className = "cd-device-view";
2003
+ this.deviceViewContainer.style.display = "none";
2004
+ this.contentContainer.appendChild(this.eventListContainer);
2005
+ this.contentContainer.appendChild(this.deviceViewContainer);
2006
+ this.element.appendChild(this.contentContainer);
2007
+ this.buildDetailOverlay();
2008
+ }
2009
+ selectTab(tab) {
2010
+ for (const [id, el] of this.tabElements.entries()) {
2011
+ el.classList.toggle("active", id === tab);
2012
+ }
2013
+ this.store.setFilter({ tab });
2014
+ this.closeDetail();
2015
+ if (tab === "device") {
2016
+ this.eventListContainer.style.display = "none";
2017
+ this.deviceViewContainer.style.display = "flex";
2018
+ this.loadDeviceDiagnostics();
2019
+ } else {
2020
+ this.eventListContainer.style.display = "flex";
2021
+ this.deviceViewContainer.style.display = "none";
2138
2022
  }
2139
- selectLevel(level) {
2140
- for (const [lvl, pill] of this.levelPills.entries()) {
2141
- pill.classList.toggle('active', lvl === level);
2142
- }
2143
- this.store.setFilter({ level });
2144
- }
2145
- updatePauseState(isPaused) {
2146
- this.statusIndicator.classList.toggle('paused', isPaused);
2147
- this.pauseBtn.classList.toggle('active', isPaused);
2148
- this.pauseBtn.title = isPaused ? 'Resume capture' : 'Pause capture';
2149
- }
2150
- bindStore() {
2151
- this.store.subscribe((events, store) => {
2152
- this.renderEventList(events);
2153
- this.renderCounts(store.getCounts());
2154
- this.updatePauseState(store.isPaused());
2155
- });
2023
+ }
2024
+ selectLevel(level) {
2025
+ for (const [lvl, pill] of this.levelPills.entries()) {
2026
+ pill.classList.toggle("active", lvl === level);
2156
2027
  }
2157
- renderCounts(counts) {
2158
- this.titleCountSpan.textContent = `(${counts.all})`;
2159
- for (const [tab, badge] of this.tabBadgeElements.entries()) {
2160
- if (tab === 'device') {
2161
- badge.style.display = 'none';
2162
- }
2163
- else {
2164
- const count = counts[tab] || 0;
2165
- badge.textContent = count > 999 ? '999+' : String(count);
2166
- }
2167
- }
2028
+ this.store.setFilter({ level });
2029
+ }
2030
+ updatePauseState(isPaused) {
2031
+ this.statusIndicator.classList.toggle("paused", isPaused);
2032
+ this.pauseBtn.classList.toggle("active", isPaused);
2033
+ this.pauseBtn.title = isPaused ? "Resume capture" : "Pause capture";
2034
+ }
2035
+ bindStore() {
2036
+ this.store.subscribe((events, store) => {
2037
+ this.renderEventList(events);
2038
+ this.renderCounts(store.getCounts());
2039
+ this.updatePauseState(store.isPaused());
2040
+ });
2041
+ }
2042
+ renderCounts(counts) {
2043
+ this.titleCountSpan.textContent = `(${counts.all})`;
2044
+ for (const [tab, badge] of this.tabBadgeElements.entries()) {
2045
+ if (tab === "device") {
2046
+ badge.style.display = "none";
2047
+ } else {
2048
+ const count = counts[tab] || 0;
2049
+ badge.textContent = count > 999 ? "999+" : String(count);
2050
+ }
2168
2051
  }
2169
- renderEventList(events) {
2170
- if (this.store.getFilter().tab === 'device') {
2171
- return;
2172
- }
2173
- this.eventListContainer.innerHTML = '';
2174
- if (events.length === 0) {
2175
- const empty = document.createElement('div');
2176
- empty.className = 'cd-empty-state';
2177
- empty.innerHTML = `
2052
+ }
2053
+ renderEventList(events) {
2054
+ if (this.store.getFilter().tab === "device") {
2055
+ return;
2056
+ }
2057
+ this.eventListContainer.innerHTML = "";
2058
+ if (events.length === 0) {
2059
+ const empty = document.createElement("div");
2060
+ empty.className = "cd-empty-state";
2061
+ empty.innerHTML = `
2178
2062
  <svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
2179
2063
  <span>No events match current filter</span>
2180
2064
  `;
2181
- this.eventListContainer.appendChild(empty);
2182
- return;
2183
- }
2184
- // Render newest items at the top
2185
- for (let i = events.length - 1; i >= 0; i--) {
2186
- const event = events[i];
2187
- const row = this.createEventRow(event);
2188
- this.eventListContainer.appendChild(row);
2189
- }
2065
+ this.eventListContainer.appendChild(empty);
2066
+ return;
2190
2067
  }
2191
- createEventRow(event) {
2192
- const row = document.createElement('div');
2193
- row.className = 'cd-event-row';
2194
- row.setAttribute('data-level', event.level || 'info');
2195
- const dot = document.createElement('div');
2196
- dot.className = 'cd-event-level-dot';
2197
- const time = document.createElement('div');
2198
- time.className = 'cd-event-time';
2199
- time.textContent = this.formatTime(event.timestamp);
2200
- const source = document.createElement('div');
2201
- source.className = 'cd-event-source';
2202
- source.textContent = event.source;
2203
- const main = document.createElement('div');
2204
- main.className = 'cd-event-main';
2205
- const type = document.createElement('div');
2206
- type.className = 'cd-event-type';
2207
- type.textContent = event.type;
2208
- main.appendChild(type);
2209
- if (event.message) {
2210
- const msg = document.createElement('div');
2211
- msg.className = 'cd-event-msg';
2212
- msg.textContent = event.message;
2213
- main.appendChild(msg);
2214
- }
2215
- row.appendChild(dot);
2216
- row.appendChild(time);
2217
- row.appendChild(source);
2218
- row.appendChild(main);
2219
- if (event.duration !== undefined) {
2220
- const duration = document.createElement('div');
2221
- duration.className = 'cd-event-duration';
2222
- duration.textContent = `${event.duration}ms`;
2223
- row.appendChild(duration);
2224
- }
2225
- row.addEventListener('click', () => {
2226
- this.showDetail(event);
2227
- });
2228
- return row;
2229
- }
2230
- buildDetailOverlay() {
2231
- this.detailOverlay = document.createElement('div');
2232
- this.detailOverlay.className = 'cd-detail-overlay';
2233
- const header = document.createElement('div');
2234
- header.className = 'cd-detail-header';
2235
- const backBtn = document.createElement('button');
2236
- backBtn.className = 'cd-detail-back';
2237
- backBtn.innerHTML = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="19" y1="12" x2="5" y2="12"/><polyline points="12 19 5 12 12 5"/></svg> Back`;
2238
- backBtn.addEventListener('click', () => this.closeDetail());
2239
- const copyAllBtn = document.createElement('button');
2240
- copyAllBtn.className = 'cd-copy-btn-sm';
2241
- copyAllBtn.textContent = 'Copy JSON';
2242
- copyAllBtn.addEventListener('click', async () => {
2243
- if (this.selectedEvent) {
2244
- await JsonViewer.copyToClipboard(JSON.stringify(this.selectedEvent, null, 2), copyAllBtn);
2245
- }
2246
- });
2247
- header.appendChild(backBtn);
2248
- header.appendChild(copyAllBtn);
2249
- this.detailOverlay.appendChild(header);
2250
- const body = document.createElement('div');
2251
- body.className = 'cd-detail-body';
2252
- this.detailOverlay.appendChild(body);
2253
- this.contentContainer.appendChild(this.detailOverlay);
2254
- }
2255
- showDetail(event) {
2256
- this.selectedEvent = event;
2257
- const body = this.detailOverlay.querySelector('.cd-detail-body');
2258
- body.innerHTML = '';
2259
- // 1. Meta Grid
2260
- const metaGrid = document.createElement('div');
2261
- metaGrid.className = 'cd-meta-grid';
2262
- const addMeta = (label, value) => {
2263
- if (value === undefined)
2264
- return;
2265
- const item = document.createElement('div');
2266
- item.className = 'cd-meta-item';
2267
- item.innerHTML = `
2068
+ for (let i = events.length - 1; i >= 0; i--) {
2069
+ const event = events[i];
2070
+ const row = this.createEventRow(event);
2071
+ this.eventListContainer.appendChild(row);
2072
+ }
2073
+ }
2074
+ createEventRow(event) {
2075
+ const row = document.createElement("div");
2076
+ row.className = "cd-event-row";
2077
+ row.setAttribute("data-level", event.level || "info");
2078
+ const dot = document.createElement("div");
2079
+ dot.className = "cd-event-level-dot";
2080
+ const time = document.createElement("div");
2081
+ time.className = "cd-event-time";
2082
+ time.textContent = this.formatTime(event.timestamp);
2083
+ const source = document.createElement("div");
2084
+ source.className = "cd-event-source";
2085
+ source.textContent = event.source;
2086
+ const main = document.createElement("div");
2087
+ main.className = "cd-event-main";
2088
+ const type = document.createElement("div");
2089
+ type.className = "cd-event-type";
2090
+ type.textContent = event.type;
2091
+ main.appendChild(type);
2092
+ if (event.message) {
2093
+ const msg = document.createElement("div");
2094
+ msg.className = "cd-event-msg";
2095
+ msg.textContent = event.message;
2096
+ main.appendChild(msg);
2097
+ }
2098
+ row.appendChild(dot);
2099
+ row.appendChild(time);
2100
+ row.appendChild(source);
2101
+ row.appendChild(main);
2102
+ if (event.duration !== void 0) {
2103
+ const duration = document.createElement("div");
2104
+ duration.className = "cd-event-duration";
2105
+ duration.textContent = `${event.duration}ms`;
2106
+ row.appendChild(duration);
2107
+ }
2108
+ row.addEventListener("click", () => {
2109
+ this.showDetail(event);
2110
+ });
2111
+ return row;
2112
+ }
2113
+ buildDetailOverlay() {
2114
+ this.detailOverlay = document.createElement("div");
2115
+ this.detailOverlay.className = "cd-detail-overlay";
2116
+ const header = document.createElement("div");
2117
+ header.className = "cd-detail-header";
2118
+ const backBtn = document.createElement("button");
2119
+ backBtn.className = "cd-detail-back";
2120
+ backBtn.innerHTML = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="19" y1="12" x2="5" y2="12"/><polyline points="12 19 5 12 12 5"/></svg> Back`;
2121
+ backBtn.addEventListener("click", () => this.closeDetail());
2122
+ const copyAllBtn = document.createElement("button");
2123
+ copyAllBtn.className = "cd-copy-btn-sm";
2124
+ copyAllBtn.textContent = "Copy JSON";
2125
+ copyAllBtn.addEventListener("click", async () => {
2126
+ if (this.selectedEvent) {
2127
+ await JsonViewer.copyToClipboard(
2128
+ JSON.stringify(this.selectedEvent, null, 2),
2129
+ copyAllBtn
2130
+ );
2131
+ }
2132
+ });
2133
+ header.appendChild(backBtn);
2134
+ header.appendChild(copyAllBtn);
2135
+ this.detailOverlay.appendChild(header);
2136
+ const body = document.createElement("div");
2137
+ body.className = "cd-detail-body";
2138
+ this.detailOverlay.appendChild(body);
2139
+ this.contentContainer.appendChild(this.detailOverlay);
2140
+ }
2141
+ showDetail(event) {
2142
+ this.selectedEvent = event;
2143
+ const body = this.detailOverlay.querySelector(".cd-detail-body");
2144
+ body.innerHTML = "";
2145
+ const metaGrid = document.createElement("div");
2146
+ metaGrid.className = "cd-meta-grid";
2147
+ const addMeta = (label, value) => {
2148
+ if (value === void 0) return;
2149
+ const item = document.createElement("div");
2150
+ item.className = "cd-meta-item";
2151
+ item.innerHTML = `
2268
2152
  <span class="cd-meta-label">${label}</span>
2269
2153
  <span class="cd-meta-val">${value}</span>
2270
2154
  `;
2271
- metaGrid.appendChild(item);
2272
- };
2273
- addMeta('Type', event.type);
2274
- addMeta('Source', event.source);
2275
- addMeta('Level', event.level);
2276
- addMeta('Time', `${this.formatTime(event.timestamp)} (${new Date(event.timestamp).toLocaleTimeString()})`);
2277
- addMeta('ID', event.id);
2278
- if (event.duration !== undefined)
2279
- addMeta('Duration', `${event.duration}ms`);
2280
- if (event.platform)
2281
- addMeta('Platform', event.platform);
2282
- body.appendChild(metaGrid);
2283
- // 2. Message
2284
- if (event.message) {
2285
- const msgSection = document.createElement('div');
2286
- msgSection.innerHTML = `<div class="cd-section-title">Message</div>`;
2287
- const msgBlock = document.createElement('div');
2288
- msgBlock.className = 'cd-msg-block';
2289
- msgBlock.textContent = event.message;
2290
- msgSection.appendChild(msgBlock);
2291
- body.appendChild(msgSection);
2292
- }
2293
- // 3. Data Payload / JSON Viewer
2294
- if (event.data !== undefined) {
2295
- const dataSection = document.createElement('div');
2296
- dataSection.innerHTML = `<div class="cd-section-title">Data Payload</div>`;
2297
- const jsonTree = JsonViewer.render(event.data, 3);
2298
- dataSection.appendChild(jsonTree);
2299
- body.appendChild(dataSection);
2300
- }
2301
- this.detailOverlay.classList.add('open');
2302
- }
2303
- closeDetail() {
2304
- this.selectedEvent = null;
2305
- this.detailOverlay.classList.remove('open');
2306
- }
2307
- async loadDeviceDiagnostics() {
2308
- if (this.isDeviceLoading)
2309
- return;
2310
- this.isDeviceLoading = true;
2311
- this.deviceViewContainer.innerHTML = `
2155
+ metaGrid.appendChild(item);
2156
+ };
2157
+ addMeta("Type", event.type);
2158
+ addMeta("Source", event.source);
2159
+ addMeta("Level", event.level);
2160
+ addMeta("Time", `${this.formatTime(event.timestamp)} (${new Date(event.timestamp).toLocaleTimeString()})`);
2161
+ addMeta("ID", event.id);
2162
+ if (event.duration !== void 0) addMeta("Duration", `${event.duration}ms`);
2163
+ if (event.platform) addMeta("Platform", event.platform);
2164
+ body.appendChild(metaGrid);
2165
+ if (event.message) {
2166
+ const msgSection = document.createElement("div");
2167
+ msgSection.innerHTML = `<div class="cd-section-title">Message</div>`;
2168
+ const msgBlock = document.createElement("div");
2169
+ msgBlock.className = "cd-msg-block";
2170
+ msgBlock.textContent = event.message;
2171
+ msgSection.appendChild(msgBlock);
2172
+ body.appendChild(msgSection);
2173
+ }
2174
+ if (event.data !== void 0) {
2175
+ const dataSection = document.createElement("div");
2176
+ dataSection.innerHTML = `<div class="cd-section-title">Data Payload</div>`;
2177
+ const jsonTree = JsonViewer.render(event.data, 3);
2178
+ dataSection.appendChild(jsonTree);
2179
+ body.appendChild(dataSection);
2180
+ }
2181
+ this.detailOverlay.classList.add("open");
2182
+ }
2183
+ closeDetail() {
2184
+ this.selectedEvent = null;
2185
+ this.detailOverlay.classList.remove("open");
2186
+ }
2187
+ async loadDeviceDiagnostics() {
2188
+ if (this.isDeviceLoading) return;
2189
+ this.isDeviceLoading = true;
2190
+ this.deviceViewContainer.innerHTML = `
2312
2191
  <div class="cd-empty-state">
2313
2192
  <span>Querying native device diagnostics...</span>
2314
2193
  </div>
2315
2194
  `;
2316
- try {
2317
- const [device, memory, webView, app, network] = await Promise.allSettled([
2318
- CapDebugNative.getDeviceInfo(),
2319
- CapDebugNative.getMemoryInfo(),
2320
- CapDebugNative.getWebViewInfo(),
2321
- CapDebugNative.getAppInfo(),
2322
- CapDebugNative.getNetworkInfo(),
2323
- ]);
2324
- this.deviceData = {
2325
- device: device.status === 'fulfilled' ? device.value : { error: String(device.reason) },
2326
- memory: memory.status === 'fulfilled' ? memory.value : { error: String(memory.reason) },
2327
- webView: webView.status === 'fulfilled' ? webView.value : { error: String(webView.reason) },
2328
- app: app.status === 'fulfilled' ? app.value : { error: String(app.reason) },
2329
- network: network.status === 'fulfilled' ? network.value : { error: String(network.reason) },
2330
- };
2331
- // Emit native diagnostics event into the store as well
2332
- this.store.add({
2333
- source: 'native',
2334
- type: 'device.diagnostics',
2335
- level: 'info',
2336
- message: 'Device diagnostics loaded',
2337
- data: this.deviceData,
2338
- });
2339
- this.renderDeviceView();
2340
- }
2341
- catch (err) {
2342
- this.deviceViewContainer.innerHTML = `
2195
+ try {
2196
+ const [device, memory, webView, app, network] = await Promise.allSettled([
2197
+ CapDebugNative.getDeviceInfo(),
2198
+ CapDebugNative.getMemoryInfo(),
2199
+ CapDebugNative.getWebViewInfo(),
2200
+ CapDebugNative.getAppInfo(),
2201
+ CapDebugNative.getNetworkInfo()
2202
+ ]);
2203
+ this.deviceData = {
2204
+ device: device.status === "fulfilled" ? device.value : { error: String(device.reason) },
2205
+ memory: memory.status === "fulfilled" ? memory.value : { error: String(memory.reason) },
2206
+ webView: webView.status === "fulfilled" ? webView.value : { error: String(webView.reason) },
2207
+ app: app.status === "fulfilled" ? app.value : { error: String(app.reason) },
2208
+ network: network.status === "fulfilled" ? network.value : { error: String(network.reason) }
2209
+ };
2210
+ this.store.add({
2211
+ source: "native",
2212
+ type: "device.diagnostics",
2213
+ level: "info",
2214
+ message: "Device diagnostics loaded",
2215
+ data: this.deviceData
2216
+ });
2217
+ this.renderDeviceView();
2218
+ } catch (err) {
2219
+ this.deviceViewContainer.innerHTML = `
2343
2220
  <div class="cd-empty-state">
2344
2221
  <span style="color: var(--cd-color-error)">Failed to load diagnostics: ${err?.message || String(err)}</span>
2345
2222
  </div>
2346
2223
  `;
2347
- }
2348
- finally {
2349
- this.isDeviceLoading = false;
2350
- }
2224
+ } finally {
2225
+ this.isDeviceLoading = false;
2351
2226
  }
2352
- renderDeviceView() {
2353
- if (!this.deviceData)
2354
- return;
2355
- this.deviceViewContainer.innerHTML = '';
2356
- // Refresh action bar
2357
- const topBar = document.createElement('div');
2358
- topBar.style.display = 'flex';
2359
- topBar.style.justifyContent = 'space-between';
2360
- topBar.style.alignItems = 'center';
2361
- const infoLabel = document.createElement('span');
2362
- infoLabel.style.fontSize = '11px';
2363
- infoLabel.style.color = 'var(--cd-text-muted)';
2364
- infoLabel.textContent = `Diagnostic snapshot: ${new Date().toLocaleTimeString()}`;
2365
- const refreshBtn = document.createElement('button');
2366
- refreshBtn.className = 'cd-copy-btn-sm';
2367
- refreshBtn.textContent = '↻ Refresh Live Data';
2368
- refreshBtn.addEventListener('click', () => this.loadDeviceDiagnostics());
2369
- topBar.appendChild(infoLabel);
2370
- topBar.appendChild(refreshBtn);
2371
- this.deviceViewContainer.appendChild(topBar);
2372
- const sections = [
2373
- { title: 'Device Information', data: this.deviceData.device },
2374
- { title: 'Memory & Heap', data: this.deviceData.memory },
2375
- { title: 'Application Info', data: this.deviceData.app },
2376
- { title: 'WebView & User Agent', data: this.deviceData.webView },
2377
- { title: 'Network State', data: this.deviceData.network },
2378
- ];
2379
- for (const { title, data } of sections) {
2380
- const card = document.createElement('div');
2381
- card.className = 'cd-device-card';
2382
- const cardHeader = document.createElement('div');
2383
- cardHeader.className = 'cd-device-card-header';
2384
- cardHeader.textContent = title;
2385
- card.appendChild(cardHeader);
2386
- if (typeof data === 'object' && data !== null) {
2387
- const grid = document.createElement('div');
2388
- grid.className = 'cd-device-grid';
2389
- for (const [k, v] of Object.entries(data)) {
2390
- if (typeof v === 'object' && v !== null)
2391
- continue;
2392
- const item = document.createElement('div');
2393
- item.className = 'cd-meta-item';
2394
- item.innerHTML = `
2227
+ }
2228
+ renderDeviceView() {
2229
+ if (!this.deviceData) return;
2230
+ this.deviceViewContainer.innerHTML = "";
2231
+ const topBar = document.createElement("div");
2232
+ topBar.style.display = "flex";
2233
+ topBar.style.justifyContent = "space-between";
2234
+ topBar.style.alignItems = "center";
2235
+ const infoLabel = document.createElement("span");
2236
+ infoLabel.style.fontSize = "11px";
2237
+ infoLabel.style.color = "var(--cd-text-muted)";
2238
+ infoLabel.textContent = `Diagnostic snapshot: ${(/* @__PURE__ */ new Date()).toLocaleTimeString()}`;
2239
+ const refreshBtn = document.createElement("button");
2240
+ refreshBtn.className = "cd-copy-btn-sm";
2241
+ refreshBtn.textContent = "\u21BB Refresh Live Data";
2242
+ refreshBtn.addEventListener("click", () => this.loadDeviceDiagnostics());
2243
+ topBar.appendChild(infoLabel);
2244
+ topBar.appendChild(refreshBtn);
2245
+ this.deviceViewContainer.appendChild(topBar);
2246
+ const sections = [
2247
+ { title: "Device Information", data: this.deviceData.device },
2248
+ { title: "Memory & Heap", data: this.deviceData.memory },
2249
+ { title: "Application Info", data: this.deviceData.app },
2250
+ { title: "WebView & User Agent", data: this.deviceData.webView },
2251
+ { title: "Network State", data: this.deviceData.network }
2252
+ ];
2253
+ for (const { title, data } of sections) {
2254
+ const card = document.createElement("div");
2255
+ card.className = "cd-device-card";
2256
+ const cardHeader = document.createElement("div");
2257
+ cardHeader.className = "cd-device-card-header";
2258
+ cardHeader.textContent = title;
2259
+ card.appendChild(cardHeader);
2260
+ if (typeof data === "object" && data !== null) {
2261
+ const grid = document.createElement("div");
2262
+ grid.className = "cd-device-grid";
2263
+ for (const [k, v] of Object.entries(data)) {
2264
+ if (typeof v === "object" && v !== null) continue;
2265
+ const item = document.createElement("div");
2266
+ item.className = "cd-meta-item";
2267
+ item.innerHTML = `
2395
2268
  <span class="cd-meta-label">${k}</span>
2396
- <span class="cd-meta-val">${v !== undefined && v !== null ? String(v) : '—'}</span>
2269
+ <span class="cd-meta-val">${v !== void 0 && v !== null ? String(v) : "\u2014"}</span>
2397
2270
  `;
2398
- grid.appendChild(item);
2399
- }
2400
- card.appendChild(grid);
2401
- // Render nested objects if present
2402
- for (const [k, v] of Object.entries(data)) {
2403
- if (typeof v === 'object' && v !== null) {
2404
- const nestedSection = document.createElement('div');
2405
- nestedSection.innerHTML = `<div style="font-size: 10px; color: var(--cd-text-muted); font-weight: 600; margin-top: 6px;">${k.toUpperCase()}</div>`;
2406
- nestedSection.appendChild(JsonViewer.render(v, 2));
2407
- card.appendChild(nestedSection);
2408
- }
2409
- }
2410
- }
2411
- else {
2412
- card.textContent = String(data);
2413
- }
2414
- this.deviceViewContainer.appendChild(card);
2415
- }
2416
- }
2417
- formatTime(timestamp) {
2418
- const d = new Date(timestamp);
2419
- const h = String(d.getHours()).padStart(2, '0');
2420
- const m = String(d.getMinutes()).padStart(2, '0');
2421
- const s = String(d.getSeconds()).padStart(2, '0');
2422
- const ms = String(d.getMilliseconds()).padStart(3, '0');
2423
- return `${h}:${m}:${s}.${ms}`;
2271
+ grid.appendChild(item);
2272
+ }
2273
+ card.appendChild(grid);
2274
+ for (const [k, v] of Object.entries(data)) {
2275
+ if (typeof v === "object" && v !== null) {
2276
+ const nestedSection = document.createElement("div");
2277
+ nestedSection.innerHTML = `<div style="font-size: 10px; color: var(--cd-text-muted); font-weight: 600; margin-top: 6px;">${k.toUpperCase()}</div>`;
2278
+ nestedSection.appendChild(JsonViewer.render(v, 2));
2279
+ card.appendChild(nestedSection);
2280
+ }
2281
+ }
2282
+ } else {
2283
+ card.textContent = String(data);
2284
+ }
2285
+ this.deviceViewContainer.appendChild(card);
2424
2286
  }
2287
+ }
2288
+ formatTime(timestamp) {
2289
+ const d = new Date(timestamp);
2290
+ const h = String(d.getHours()).padStart(2, "0");
2291
+ const m = String(d.getMinutes()).padStart(2, "0");
2292
+ const s = String(d.getSeconds()).padStart(2, "0");
2293
+ const ms = String(d.getMilliseconds()).padStart(3, "0");
2294
+ return `${h}:${m}:${s}.${ms}`;
2295
+ }
2425
2296
  }
2426
2297
 
2427
2298
  class CapDebugViewerElement extends HTMLElement {
2428
- static TAG = 'cap-debug-viewer';
2429
- shadowRootNode;
2430
- floatingButton = null;
2431
- debugPanel = null;
2432
- store = null;
2433
- unsubscribeStore = null;
2434
- constructor() {
2435
- super();
2436
- this.shadowRootNode = this.attachShadow({ mode: 'open' });
2437
- }
2438
- getStore() {
2439
- return this.store;
2440
- }
2441
- init(store, options = {}) {
2442
- this.store = store;
2443
- // Inject isolated styles
2444
- const styleEl = document.createElement('style');
2445
- styleEl.textContent = VIEWER_STYLES;
2446
- this.shadowRootNode.appendChild(styleEl);
2447
- // Create Panel
2448
- this.debugPanel = new DebugPanel(store, {
2449
- defaultTab: options.defaultTab || 'all',
2450
- onClose: () => {
2451
- // Can handle panel close events if needed
2452
- },
2453
- });
2454
- this.shadowRootNode.appendChild(this.debugPanel.getBackdrop());
2455
- this.shadowRootNode.appendChild(this.debugPanel.getElement());
2456
- // Create Floating Button (if enabled)
2457
- if (options.button !== false) {
2458
- this.floatingButton = new FloatingButton({
2459
- position: options.position,
2460
- persistPosition: options.persistPosition !== false,
2461
- onClick: () => {
2462
- this.toggle();
2463
- },
2464
- });
2465
- this.shadowRootNode.appendChild(this.floatingButton.getElement());
2466
- }
2467
- // Subscribe to error counts for the button badge
2468
- this.unsubscribeStore = store.subscribe((_events, s) => {
2469
- const counts = s.getCounts();
2470
- if (this.floatingButton) {
2471
- this.floatingButton.setErrorCount(counts.errors);
2472
- }
2473
- });
2299
+ static TAG = "cap-debug-viewer";
2300
+ shadowRootNode;
2301
+ floatingButton = null;
2302
+ debugPanel = null;
2303
+ store = null;
2304
+ unsubscribeStore = null;
2305
+ constructor() {
2306
+ super();
2307
+ this.shadowRootNode = this.attachShadow({ mode: "open" });
2308
+ }
2309
+ getStore() {
2310
+ return this.store;
2311
+ }
2312
+ init(store, options = {}) {
2313
+ this.store = store;
2314
+ const styleEl = document.createElement("style");
2315
+ styleEl.textContent = VIEWER_STYLES;
2316
+ this.shadowRootNode.appendChild(styleEl);
2317
+ this.debugPanel = new DebugPanel(store, {
2318
+ defaultTab: options.defaultTab || "all",
2319
+ onClose: () => {
2320
+ }
2321
+ });
2322
+ this.shadowRootNode.appendChild(this.debugPanel.getBackdrop());
2323
+ this.shadowRootNode.appendChild(this.debugPanel.getElement());
2324
+ if (options.button !== false) {
2325
+ this.floatingButton = new FloatingButton({
2326
+ position: options.position,
2327
+ persistPosition: options.persistPosition !== false,
2328
+ onClick: () => {
2329
+ this.toggle();
2330
+ }
2331
+ });
2332
+ this.shadowRootNode.appendChild(this.floatingButton.getElement());
2333
+ }
2334
+ this.unsubscribeStore = store.subscribe((_events, s) => {
2335
+ const counts = s.getCounts();
2336
+ if (this.floatingButton) {
2337
+ this.floatingButton.setErrorCount(counts.errors);
2338
+ }
2339
+ });
2340
+ }
2341
+ show() {
2342
+ if (this.debugPanel) {
2343
+ this.debugPanel.open();
2474
2344
  }
2475
- show() {
2476
- if (this.debugPanel) {
2477
- this.debugPanel.open();
2478
- }
2345
+ }
2346
+ hide() {
2347
+ if (this.debugPanel) {
2348
+ this.debugPanel.close();
2479
2349
  }
2480
- hide() {
2481
- if (this.debugPanel) {
2482
- this.debugPanel.close();
2483
- }
2350
+ }
2351
+ toggle() {
2352
+ if (this.debugPanel) {
2353
+ return this.debugPanel.toggle();
2484
2354
  }
2485
- toggle() {
2486
- if (this.debugPanel) {
2487
- return this.debugPanel.toggle();
2488
- }
2489
- return false;
2355
+ return false;
2356
+ }
2357
+ showButton() {
2358
+ if (this.floatingButton) {
2359
+ this.floatingButton.show();
2490
2360
  }
2491
- showButton() {
2492
- if (this.floatingButton) {
2493
- this.floatingButton.show();
2494
- }
2361
+ }
2362
+ hideButton() {
2363
+ if (this.floatingButton) {
2364
+ this.floatingButton.hide();
2365
+ }
2366
+ }
2367
+ disconnectedCallback() {
2368
+ if (this.unsubscribeStore) {
2369
+ this.unsubscribeStore();
2370
+ this.unsubscribeStore = null;
2371
+ }
2372
+ }
2373
+ }
2374
+ function registerCustomElement() {
2375
+ if (typeof customElements !== "undefined" && !customElements.get(CapDebugViewerElement.TAG)) {
2376
+ customElements.define(CapDebugViewerElement.TAG, CapDebugViewerElement);
2377
+ }
2378
+ }
2379
+
2380
+ class HttpSseSignalingClient {
2381
+ baseUrl;
2382
+ sessionId = "";
2383
+ secret = "";
2384
+ role = "peer";
2385
+ eventSource = null;
2386
+ listeners = /* @__PURE__ */ new Set();
2387
+ isClosed = false;
2388
+ pollTimer = null;
2389
+ constructor(baseUrl) {
2390
+ this.baseUrl = baseUrl.replace(/\/+$/, "");
2391
+ }
2392
+ async createSession(ttlSeconds = 300) {
2393
+ this.role = "host";
2394
+ const res = await fetch(`${this.baseUrl}/api/pair/create`, {
2395
+ method: "POST",
2396
+ headers: { "Content-Type": "application/json" },
2397
+ body: JSON.stringify({ ttl: ttlSeconds })
2398
+ });
2399
+ if (!res.ok) {
2400
+ throw new Error(`Failed to create pairing session: ${res.statusText}`);
2401
+ }
2402
+ const data = await res.json();
2403
+ this.sessionId = data.sessionId;
2404
+ this.secret = data.secret;
2405
+ this.connectEventStream();
2406
+ return data;
2407
+ }
2408
+ async joinSession(sessionId, secret) {
2409
+ this.role = "peer";
2410
+ this.sessionId = sessionId;
2411
+ this.secret = secret;
2412
+ const res = await fetch(`${this.baseUrl}/api/pair/join`, {
2413
+ method: "POST",
2414
+ headers: { "Content-Type": "application/json" },
2415
+ body: JSON.stringify({ sessionId, secret })
2416
+ });
2417
+ if (!res.ok) {
2418
+ const err = await res.text().catch(() => "");
2419
+ throw new Error(`Failed to join pairing session: ${err || res.statusText}`);
2420
+ }
2421
+ this.connectEventStream();
2422
+ }
2423
+ async send(message) {
2424
+ if (this.isClosed) return;
2425
+ message.sessionId = this.sessionId;
2426
+ message.secret = this.secret;
2427
+ message.sender = this.role;
2428
+ message.timestamp = Date.now();
2429
+ await fetch(`${this.baseUrl}/api/pair/signal`, {
2430
+ method: "POST",
2431
+ headers: { "Content-Type": "application/json" },
2432
+ body: JSON.stringify(message)
2433
+ });
2434
+ }
2435
+ onMessage(callback) {
2436
+ this.listeners.add(callback);
2437
+ return () => {
2438
+ this.listeners.delete(callback);
2439
+ };
2440
+ }
2441
+ close() {
2442
+ this.isClosed = true;
2443
+ if (this.eventSource) {
2444
+ this.eventSource.close();
2445
+ this.eventSource = null;
2495
2446
  }
2496
- hideButton() {
2497
- if (this.floatingButton) {
2498
- this.floatingButton.hide();
2447
+ if (this.pollTimer) {
2448
+ clearInterval(this.pollTimer);
2449
+ this.pollTimer = null;
2450
+ }
2451
+ this.listeners.clear();
2452
+ }
2453
+ connectEventStream() {
2454
+ if (typeof EventSource !== "undefined") {
2455
+ const sseUrl = `${this.baseUrl}/api/pair/events?session=${encodeURIComponent(this.sessionId)}&secret=${encodeURIComponent(this.secret)}&role=${this.role}`;
2456
+ this.eventSource = new EventSource(sseUrl);
2457
+ this.eventSource.onmessage = (event) => {
2458
+ if (!event.data) return;
2459
+ try {
2460
+ const msg = JSON.parse(event.data);
2461
+ this.dispatchMessage(msg);
2462
+ } catch {
2499
2463
  }
2464
+ };
2465
+ this.eventSource.onerror = () => {
2466
+ };
2467
+ } else {
2468
+ this.startPolling();
2500
2469
  }
2501
- disconnectedCallback() {
2502
- if (this.unsubscribeStore) {
2503
- this.unsubscribeStore();
2504
- this.unsubscribeStore = null;
2470
+ }
2471
+ startPolling() {
2472
+ if (this.pollTimer) return;
2473
+ this.pollTimer = setInterval(async () => {
2474
+ if (this.isClosed || !this.sessionId) return;
2475
+ try {
2476
+ const res = await fetch(
2477
+ `${this.baseUrl}/api/pair/poll?session=${encodeURIComponent(this.sessionId)}&secret=${encodeURIComponent(this.secret)}&role=${this.role}`
2478
+ );
2479
+ if (res.ok) {
2480
+ const messages = await res.json();
2481
+ if (Array.isArray(messages)) {
2482
+ for (const msg of messages) {
2483
+ this.dispatchMessage(msg);
2484
+ }
2485
+ }
2505
2486
  }
2487
+ } catch {
2488
+ }
2489
+ }, 1e3);
2490
+ }
2491
+ dispatchMessage(message) {
2492
+ for (const listener of this.listeners) {
2493
+ try {
2494
+ listener(message);
2495
+ } catch (err) {
2496
+ console.error("[CapDebug Signaling] Listener error:", err);
2497
+ }
2506
2498
  }
2499
+ }
2507
2500
  }
2508
- function registerCustomElement() {
2509
- if (typeof customElements !== 'undefined' && !customElements.get(CapDebugViewerElement.TAG)) {
2510
- customElements.define(CapDebugViewerElement.TAG, CapDebugViewerElement);
2501
+
2502
+ const PROTOCOL_VERSION = 1;
2503
+ class RemoteProtocol {
2504
+ static createHello(capdebugVersion = "0.2.0", platform = "web", deviceName) {
2505
+ return {
2506
+ v: 1,
2507
+ type: "hello",
2508
+ payload: {
2509
+ capdebugVersion,
2510
+ platform,
2511
+ deviceName: deviceName || (typeof navigator !== "undefined" ? navigator.userAgent : "Unknown Device")
2512
+ },
2513
+ timestamp: Date.now()
2514
+ };
2515
+ }
2516
+ static createHelloAck() {
2517
+ return {
2518
+ v: 1,
2519
+ type: "hello-ack",
2520
+ payload: {
2521
+ serverTime: Date.now()
2522
+ },
2523
+ timestamp: Date.now()
2524
+ };
2525
+ }
2526
+ static createEvent(event) {
2527
+ return {
2528
+ v: 1,
2529
+ type: "event",
2530
+ event,
2531
+ timestamp: Date.now()
2532
+ };
2533
+ }
2534
+ static createBatch(events) {
2535
+ return {
2536
+ v: 1,
2537
+ type: "events-batch",
2538
+ events,
2539
+ timestamp: Date.now()
2540
+ };
2541
+ }
2542
+ static createControl(command) {
2543
+ return {
2544
+ v: 1,
2545
+ type: "control",
2546
+ command,
2547
+ timestamp: Date.now()
2548
+ };
2549
+ }
2550
+ static createPing(timestamp = Date.now()) {
2551
+ return {
2552
+ v: 1,
2553
+ type: "ping",
2554
+ timestamp
2555
+ };
2556
+ }
2557
+ static createPong(pingTimestamp) {
2558
+ return {
2559
+ v: 1,
2560
+ type: "pong",
2561
+ payload: { pingTimestamp },
2562
+ timestamp: Date.now()
2563
+ };
2564
+ }
2565
+ static serialize(message) {
2566
+ return JSON.stringify(message);
2567
+ }
2568
+ static parse(raw) {
2569
+ try {
2570
+ const parsed = JSON.parse(raw);
2571
+ if (!parsed || typeof parsed !== "object") {
2572
+ return null;
2573
+ }
2574
+ if (parsed.v !== PROTOCOL_VERSION) {
2575
+ console.warn(`[CapDebug Remote] Unsupported protocol version: ${parsed.v}`);
2576
+ return null;
2577
+ }
2578
+ return parsed;
2579
+ } catch {
2580
+ return null;
2511
2581
  }
2582
+ }
2512
2583
  }
2513
2584
 
2514
- class CapDebugManager {
2515
- store;
2516
- viewerElement = null;
2517
- isMounted = false;
2518
- customEventCollector;
2519
- consoleCollector;
2520
- errorCollector;
2521
- networkCollector;
2522
- browserEventCollector;
2523
- bridgeCollector;
2524
- constructor() {
2525
- this.store = new CapDebugStore();
2526
- this.customEventCollector = new CustomEventCollector(this.store);
2527
- this.consoleCollector = new ConsoleCollector();
2528
- this.errorCollector = new ErrorCollector();
2529
- this.networkCollector = new NetworkCollector();
2530
- this.browserEventCollector = new BrowserEventCollector();
2531
- this.bridgeCollector = new BridgeCollector();
2532
- }
2533
- get native() {
2534
- return CapDebugNative;
2535
- }
2536
- get currentStore() {
2537
- return this.store;
2538
- }
2539
- /**
2540
- * Mounts the CapDebug UI into the DOM and activates collectors.
2541
- */
2542
- mount(options = {}) {
2543
- if (typeof window === 'undefined' || typeof document === 'undefined') {
2544
- return;
2545
- }
2546
- if (this.isMounted) {
2547
- this.unmount();
2548
- }
2549
- // Configure store
2550
- if (options.maxEvents) {
2551
- this.store.setMaxEvents(options.maxEvents);
2552
- }
2553
- if (options.customMaskKeys) {
2554
- this.store.setCustomMaskKeys(options.customMaskKeys);
2555
- }
2556
- // 1. Start core CustomEvent bus listener
2557
- this.customEventCollector.start();
2558
- // 2. Start optional collectors
2559
- if (options.console !== false) {
2560
- this.consoleCollector.start();
2561
- }
2562
- if (options.errors !== false) {
2563
- this.errorCollector.start();
2564
- }
2565
- if (options.network) {
2566
- const netOpts = typeof options.network === 'object' ? options.network : {};
2567
- this.networkCollector.setOptions(netOpts);
2568
- this.networkCollector.start();
2569
- }
2570
- if (options.bridge) {
2571
- this.bridgeCollector.start();
2572
- }
2573
- this.browserEventCollector.start();
2574
- // 3. Register and create Web Component
2575
- registerCustomElement();
2576
- this.viewerElement = document.createElement(CapDebugViewerElement.TAG);
2577
- this.viewerElement.init(this.store, options);
2578
- document.body.appendChild(this.viewerElement);
2579
- this.isMounted = true;
2580
- }
2581
- /**
2582
- * Unmounts CapDebug UI and stops all collectors.
2583
- */
2584
- unmount() {
2585
- if (!this.isMounted)
2586
- return;
2587
- if (this.viewerElement && this.viewerElement.parentNode) {
2588
- this.viewerElement.parentNode.removeChild(this.viewerElement);
2589
- this.viewerElement = null;
2590
- }
2591
- this.customEventCollector.stop();
2592
- this.consoleCollector.stop();
2593
- this.errorCollector.stop();
2594
- this.networkCollector.stop();
2595
- this.browserEventCollector.stop();
2596
- this.bridgeCollector.stop();
2597
- this.isMounted = false;
2598
- }
2599
- /**
2600
- * Opens the debug viewer drawer.
2601
- */
2602
- show() {
2603
- if (this.viewerElement) {
2604
- this.viewerElement.show();
2605
- }
2585
+ class RemoteEventBuffer {
2586
+ buffer = [];
2587
+ maxCapacity;
2588
+ droppedCount = 0;
2589
+ constructor(maxCapacity = 100) {
2590
+ this.maxCapacity = Math.max(1, maxCapacity);
2591
+ }
2592
+ push(event) {
2593
+ if (this.buffer.length >= this.maxCapacity) {
2594
+ this.buffer.shift();
2606
2595
  }
2607
- /**
2608
- * Closes the debug viewer drawer.
2609
- */
2610
- hide() {
2611
- if (this.viewerElement) {
2612
- this.viewerElement.hide();
2613
- }
2596
+ this.buffer.push(event);
2597
+ }
2598
+ flush() {
2599
+ const items = [...this.buffer];
2600
+ this.buffer = [];
2601
+ return items;
2602
+ }
2603
+ clear() {
2604
+ this.buffer = [];
2605
+ this.droppedCount = 0;
2606
+ }
2607
+ size() {
2608
+ return this.buffer.length;
2609
+ }
2610
+ getDroppedCount() {
2611
+ return this.droppedCount;
2612
+ }
2613
+ incrementDropped() {
2614
+ this.droppedCount++;
2615
+ }
2616
+ }
2617
+
2618
+ class WebRTCTransport {
2619
+ id = "webrtc";
2620
+ pc = null;
2621
+ signaling = null;
2622
+ config = null;
2623
+ eventsChannel = null;
2624
+ controlChannel = null;
2625
+ status = "disconnected";
2626
+ buffer = new RemoteEventBuffer(100);
2627
+ eventListeners = /* @__PURE__ */ new Set();
2628
+ controlListeners = /* @__PURE__ */ new Set();
2629
+ statusListeners = /* @__PURE__ */ new Set();
2630
+ diagnosticsListeners = /* @__PURE__ */ new Set();
2631
+ pingTimer = null;
2632
+ statsTimer = null;
2633
+ rttMs = 0;
2634
+ totalEventsSent = 0;
2635
+ totalEventsReceived = 0;
2636
+ connectedAt = 0;
2637
+ remoteDeviceName = "Unknown Device";
2638
+ remotePlatform = "unknown";
2639
+ diagnostics = {
2640
+ transport: "webrtc",
2641
+ status: "disconnected",
2642
+ route: "unknown"
2643
+ };
2644
+ async connect(config) {
2645
+ this.config = config;
2646
+ this.setStatus("connecting");
2647
+ const signalingUrl = config.signalingUrl || (typeof window !== "undefined" ? window.location.origin : "http://localhost:4783");
2648
+ this.signaling = new HttpSseSignalingClient(signalingUrl);
2649
+ this.signaling.onMessage((msg) => this.handleSignalingMessage(msg));
2650
+ if (config.isHost) {
2651
+ this.setStatus("signaling");
2652
+ } else {
2653
+ this.setStatus("signaling");
2654
+ await this.signaling.joinSession(config.sessionId, config.secret);
2614
2655
  }
2615
- /**
2616
- * Toggles the debug viewer drawer open/closed.
2617
- */
2618
- toggle() {
2619
- if (this.viewerElement) {
2620
- return this.viewerElement.toggle();
2621
- }
2622
- return false;
2656
+ }
2657
+ send(event) {
2658
+ if (this.eventsChannel && this.eventsChannel.readyState === "open") {
2659
+ if (this.eventsChannel.bufferedAmount > 256 * 1024) {
2660
+ if (event.level === "debug" || event.level === "info") {
2661
+ this.buffer.incrementDropped();
2662
+ return;
2663
+ }
2664
+ }
2665
+ const msg = RemoteProtocol.createEvent(event);
2666
+ try {
2667
+ this.eventsChannel.send(RemoteProtocol.serialize(msg));
2668
+ this.totalEventsSent++;
2669
+ } catch (err) {
2670
+ console.error("[CapDebug Remote] Send error:", err);
2671
+ }
2672
+ } else {
2673
+ this.buffer.push(event);
2623
2674
  }
2624
- /**
2625
- * Dispatches a debug event through the CustomEvent bus.
2626
- */
2627
- emit(event) {
2628
- if (typeof window === 'undefined')
2629
- return;
2630
- window.dispatchEvent(new CustomEvent('capdebug', {
2631
- detail: {
2632
- id: event.id,
2633
- timestamp: event.timestamp || Date.now(),
2634
- source: event.source || 'web',
2635
- type: event.type,
2636
- level: event.level || 'info',
2637
- message: event.message,
2638
- data: event.data,
2639
- duration: event.duration,
2640
- platform: event.platform,
2641
- },
2642
- }));
2643
- }
2644
- /**
2645
- * Clears all recorded events from the store.
2646
- */
2647
- clear() {
2648
- this.store.clear();
2649
- }
2650
- /**
2651
- * Pauses capturing new events.
2652
- */
2653
- pause() {
2654
- this.store.pause();
2655
- }
2656
- /**
2657
- * Resumes capturing events.
2658
- */
2659
- resume() {
2660
- this.store.resume();
2661
- }
2662
- captureConsole() {
2663
- this.consoleCollector.start();
2664
- }
2665
- releaseConsole() {
2666
- this.consoleCollector.stop();
2667
- }
2668
- captureErrors() {
2669
- this.errorCollector.start();
2670
- }
2671
- releaseErrors() {
2672
- this.errorCollector.stop();
2673
- }
2674
- captureFetch(options) {
2675
- if (options) {
2676
- this.networkCollector.setOptions(options);
2675
+ }
2676
+ sendControl(command) {
2677
+ if (this.controlChannel && this.controlChannel.readyState === "open") {
2678
+ const msg = RemoteProtocol.createControl(command);
2679
+ try {
2680
+ this.controlChannel.send(RemoteProtocol.serialize(msg));
2681
+ } catch (err) {
2682
+ console.error("[CapDebug Remote] Control send error:", err);
2683
+ }
2684
+ }
2685
+ }
2686
+ disconnect() {
2687
+ this.stopTimers();
2688
+ if (this.controlChannel) {
2689
+ try {
2690
+ this.sendControl({ type: "disconnect" });
2691
+ } catch {
2692
+ }
2693
+ }
2694
+ if (this.signaling) {
2695
+ this.signaling.close();
2696
+ this.signaling = null;
2697
+ }
2698
+ if (this.eventsChannel) {
2699
+ this.eventsChannel.close();
2700
+ this.eventsChannel = null;
2701
+ }
2702
+ if (this.controlChannel) {
2703
+ this.controlChannel.close();
2704
+ this.controlChannel = null;
2705
+ }
2706
+ if (this.pc) {
2707
+ this.pc.close();
2708
+ this.pc = null;
2709
+ }
2710
+ this.setStatus("disconnected");
2711
+ }
2712
+ getStatus() {
2713
+ return this.status;
2714
+ }
2715
+ getDiagnostics() {
2716
+ return {
2717
+ ...this.diagnostics,
2718
+ status: this.status,
2719
+ rttMs: this.rttMs,
2720
+ bufferedAmountBytes: this.eventsChannel?.bufferedAmount || 0,
2721
+ droppedEventsCount: this.buffer.getDroppedCount(),
2722
+ totalEventsSent: this.totalEventsSent,
2723
+ totalEventsReceived: this.totalEventsReceived,
2724
+ connectedAt: this.connectedAt,
2725
+ deviceName: this.remoteDeviceName,
2726
+ platform: this.remotePlatform
2727
+ };
2728
+ }
2729
+ onEvent(callback) {
2730
+ this.eventListeners.add(callback);
2731
+ return () => this.eventListeners.delete(callback);
2732
+ }
2733
+ onControl(callback) {
2734
+ this.controlListeners.add(callback);
2735
+ return () => this.controlListeners.delete(callback);
2736
+ }
2737
+ onStatusChange(callback) {
2738
+ this.statusListeners.add(callback);
2739
+ callback(this.status);
2740
+ return () => this.statusListeners.delete(callback);
2741
+ }
2742
+ onDiagnostics(callback) {
2743
+ this.diagnosticsListeners.add(callback);
2744
+ return () => this.diagnosticsListeners.delete(callback);
2745
+ }
2746
+ async handleSignalingMessage(msg) {
2747
+ switch (msg.type) {
2748
+ case "peer-joined":
2749
+ if (this.config?.isHost) {
2750
+ await this.startHandshakeAsInitiator();
2751
+ }
2752
+ break;
2753
+ case "offer":
2754
+ if (!this.config?.isHost) {
2755
+ await this.handleOffer(msg.payload);
2756
+ }
2757
+ break;
2758
+ case "answer":
2759
+ if (this.config?.isHost && this.pc) {
2760
+ await this.pc.setRemoteDescription(new RTCSessionDescription(msg.payload));
2761
+ }
2762
+ break;
2763
+ case "ice-candidate":
2764
+ if (this.pc && msg.payload) {
2765
+ try {
2766
+ await this.pc.addIceCandidate(new RTCIceCandidate(msg.payload));
2767
+ } catch (err) {
2768
+ console.warn("[CapDebug Remote] ICE Candidate error:", err);
2769
+ }
2770
+ }
2771
+ break;
2772
+ case "disconnect":
2773
+ this.disconnect();
2774
+ break;
2775
+ }
2776
+ }
2777
+ initPeerConnection() {
2778
+ const iceServers = this.config?.iceServers || [
2779
+ { urls: "stun:stun.l.google.com:19302" },
2780
+ { urls: "stun:global.stun.twilio.com:3478" }
2781
+ ];
2782
+ const pc = new RTCPeerConnection({ iceServers });
2783
+ pc.onicecandidate = (event) => {
2784
+ if (event.candidate && this.signaling) {
2785
+ this.signaling.send({
2786
+ sessionId: this.config.sessionId,
2787
+ secret: this.config.secret,
2788
+ type: "ice-candidate",
2789
+ sender: this.config.isHost ? "host" : "peer",
2790
+ payload: event.candidate.toJSON()
2791
+ });
2792
+ }
2793
+ };
2794
+ pc.onconnectionstatechange = () => {
2795
+ this.updateDiagnostics();
2796
+ if (pc.connectionState === "failed") {
2797
+ this.setStatus("failed");
2798
+ } else if (pc.connectionState === "closed" || pc.connectionState === "disconnected") {
2799
+ this.setStatus("disconnected");
2800
+ }
2801
+ };
2802
+ pc.oniceconnectionstatechange = () => {
2803
+ this.updateDiagnostics();
2804
+ };
2805
+ this.pc = pc;
2806
+ return pc;
2807
+ }
2808
+ async startHandshakeAsInitiator() {
2809
+ const pc = this.initPeerConnection();
2810
+ this.eventsChannel = pc.createDataChannel("events", { ordered: true });
2811
+ this.controlChannel = pc.createDataChannel("control", { ordered: true });
2812
+ this.setupDataChannel(this.eventsChannel, "events");
2813
+ this.setupDataChannel(this.controlChannel, "control");
2814
+ const offer = await pc.createOffer();
2815
+ await pc.setLocalDescription(offer);
2816
+ if (this.signaling) {
2817
+ await this.signaling.send({
2818
+ sessionId: this.config.sessionId,
2819
+ secret: this.config.secret,
2820
+ type: "offer",
2821
+ sender: "host",
2822
+ payload: offer
2823
+ });
2824
+ }
2825
+ }
2826
+ async handleOffer(offerSdp) {
2827
+ const pc = this.initPeerConnection();
2828
+ pc.ondatachannel = (event) => {
2829
+ const channel = event.channel;
2830
+ if (channel.label === "events") {
2831
+ this.eventsChannel = channel;
2832
+ this.setupDataChannel(channel, "events");
2833
+ } else if (channel.label === "control") {
2834
+ this.controlChannel = channel;
2835
+ this.setupDataChannel(channel, "control");
2836
+ }
2837
+ };
2838
+ await pc.setRemoteDescription(new RTCSessionDescription(offerSdp));
2839
+ const answer = await pc.createAnswer();
2840
+ await pc.setLocalDescription(answer);
2841
+ if (this.signaling) {
2842
+ await this.signaling.send({
2843
+ sessionId: this.config.sessionId,
2844
+ secret: this.config.secret,
2845
+ type: "answer",
2846
+ sender: "peer",
2847
+ payload: answer
2848
+ });
2849
+ }
2850
+ }
2851
+ setupDataChannel(channel, label) {
2852
+ channel.onopen = () => {
2853
+ if (this.eventsChannel?.readyState === "open" && this.controlChannel?.readyState === "open") {
2854
+ this.connectedAt = Date.now();
2855
+ this.setStatus("connected");
2856
+ this.startTimers();
2857
+ if (!this.config?.isHost) {
2858
+ const hello = RemoteProtocol.createHello("0.2.0", "mobile");
2859
+ channel.send(RemoteProtocol.serialize(hello));
2860
+ const buffered = this.buffer.flush();
2861
+ if (buffered.length > 0 && this.eventsChannel?.readyState === "open") {
2862
+ const batch = RemoteProtocol.createBatch(buffered);
2863
+ this.eventsChannel.send(RemoteProtocol.serialize(batch));
2864
+ this.totalEventsSent += buffered.length;
2865
+ }
2866
+ }
2867
+ }
2868
+ };
2869
+ channel.onclose = () => {
2870
+ this.setStatus("disconnected");
2871
+ };
2872
+ channel.onmessage = (e) => {
2873
+ const raw = e.data;
2874
+ if (typeof raw !== "string") return;
2875
+ const message = RemoteProtocol.parse(raw);
2876
+ if (!message) return;
2877
+ if (label === "events") {
2878
+ if (message.type === "event" && message.event) {
2879
+ this.totalEventsReceived++;
2880
+ for (const listener of this.eventListeners) {
2881
+ listener(message.event);
2882
+ }
2883
+ } else if (message.type === "events-batch" && Array.isArray(message.events)) {
2884
+ this.totalEventsReceived += message.events.length;
2885
+ for (const ev of message.events) {
2886
+ for (const listener of this.eventListeners) {
2887
+ listener(ev);
2888
+ }
2889
+ }
2677
2890
  }
2678
- this.networkCollector.start();
2891
+ } else if (label === "control") {
2892
+ this.handleControlMessage(message);
2893
+ }
2894
+ };
2895
+ }
2896
+ handleControlMessage(message) {
2897
+ if (message.type === "control" && message.command) {
2898
+ for (const listener of this.controlListeners) {
2899
+ listener(message.command);
2900
+ }
2901
+ } else if (message.type === "hello") {
2902
+ this.remoteDeviceName = message.payload?.deviceName || "Mobile Device";
2903
+ this.remotePlatform = message.payload?.platform || "mobile";
2904
+ if (this.controlChannel?.readyState === "open") {
2905
+ this.controlChannel.send(RemoteProtocol.serialize(RemoteProtocol.createHelloAck()));
2906
+ }
2907
+ this.updateDiagnostics();
2908
+ } else if (message.type === "hello-ack") {
2909
+ this.updateDiagnostics();
2910
+ } else if (message.type === "ping") {
2911
+ if (this.controlChannel?.readyState === "open") {
2912
+ const pong = RemoteProtocol.createPong(message.timestamp || Date.now());
2913
+ this.controlChannel.send(RemoteProtocol.serialize(pong));
2914
+ }
2915
+ } else if (message.type === "pong") {
2916
+ const pingTime = message.payload?.pingTimestamp || message.timestamp;
2917
+ if (pingTime) {
2918
+ this.rttMs = Math.max(1, Date.now() - pingTime);
2919
+ this.updateDiagnostics();
2920
+ }
2921
+ }
2922
+ }
2923
+ startTimers() {
2924
+ this.stopTimers();
2925
+ this.pingTimer = setInterval(() => {
2926
+ if (this.controlChannel?.readyState === "open") {
2927
+ this.controlChannel.send(RemoteProtocol.serialize(RemoteProtocol.createPing(Date.now())));
2928
+ }
2929
+ }, 2e3);
2930
+ this.statsTimer = setInterval(() => {
2931
+ this.queryWebRTCStats();
2932
+ }, 3e3);
2933
+ }
2934
+ stopTimers() {
2935
+ if (this.pingTimer) {
2936
+ clearInterval(this.pingTimer);
2937
+ this.pingTimer = null;
2938
+ }
2939
+ if (this.statsTimer) {
2940
+ clearInterval(this.statsTimer);
2941
+ this.statsTimer = null;
2942
+ }
2943
+ }
2944
+ async queryWebRTCStats() {
2945
+ if (!this.pc) return;
2946
+ try {
2947
+ const stats = await this.pc.getStats();
2948
+ let candidateType = "unknown";
2949
+ let protocol = "UDP";
2950
+ let route = "unknown";
2951
+ stats.forEach((report) => {
2952
+ if (report.type === "candidate-pair" && (report.selected || report.state === "succeeded")) {
2953
+ const localCandidate = stats.get(report.localCandidateId);
2954
+ if (localCandidate) {
2955
+ candidateType = localCandidate.candidateType || "host";
2956
+ protocol = localCandidate.protocol || "UDP";
2957
+ if (candidateType === "host") {
2958
+ route = "direct / LAN";
2959
+ } else if (candidateType === "srflx" || candidateType === "prflx") {
2960
+ route = "STUN / WAN";
2961
+ } else if (candidateType === "relay") {
2962
+ route = "relay / TURN";
2963
+ }
2964
+ }
2965
+ if (report.currentRoundTripTime) {
2966
+ this.rttMs = Math.round(report.currentRoundTripTime * 1e3);
2967
+ }
2968
+ }
2969
+ });
2970
+ this.diagnostics = {
2971
+ ...this.diagnostics,
2972
+ candidateType,
2973
+ protocol,
2974
+ route
2975
+ };
2976
+ this.updateDiagnostics();
2977
+ } catch {
2679
2978
  }
2680
- releaseFetch() {
2681
- this.networkCollector.stop();
2979
+ }
2980
+ setStatus(status) {
2981
+ this.status = status;
2982
+ this.updateDiagnostics();
2983
+ for (const listener of this.statusListeners) {
2984
+ listener(status);
2682
2985
  }
2683
- observeEvent(eventName) {
2684
- this.browserEventCollector.observe(eventName);
2986
+ }
2987
+ updateDiagnostics() {
2988
+ if (this.pc) {
2989
+ this.diagnostics.connectionState = this.pc.connectionState;
2990
+ this.diagnostics.iceConnectionState = this.pc.iceConnectionState;
2991
+ this.diagnostics.iceGatheringState = this.pc.iceGatheringState;
2992
+ this.diagnostics.signalingState = this.pc.signalingState;
2685
2993
  }
2686
- unobserveEvent(eventName) {
2687
- this.browserEventCollector.unobserve(eventName);
2994
+ const fullDiag = this.getDiagnostics();
2995
+ for (const listener of this.diagnosticsListeners) {
2996
+ listener(fullDiag);
2688
2997
  }
2998
+ }
2689
2999
  }
2690
- const CapDebug = new CapDebugManager();
2691
3000
 
2692
- function formatBytes(bytes) {
2693
- if (bytes === 0)
2694
- return '0 B';
2695
- const k = 1024;
2696
- const sizes = ['B', 'KB', 'MB', 'GB'];
2697
- const i = Math.floor(Math.log(bytes) / Math.log(k));
2698
- return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
2699
- }
2700
- class CapDebugWeb extends core.WebPlugin {
2701
- async getDeviceInfo() {
2702
- const nav = typeof navigator !== 'undefined' ? navigator : {};
3001
+ class PairingHelper {
3002
+ /**
3003
+ * Creates a new pairing session with given TTL (default 5 minutes).
3004
+ */
3005
+ static createSession(ttlMs = 5 * 60 * 1e3) {
3006
+ const now = Date.now();
3007
+ return {
3008
+ id: this.generateSessionId(),
3009
+ createdAt: now,
3010
+ expiresAt: now + ttlMs,
3011
+ status: "waiting"
3012
+ };
3013
+ }
3014
+ /**
3015
+ * Generates a short, human-readable session ID (e.g. "K7P4-M2" or "7HQ2-9X").
3016
+ */
3017
+ static generateSessionId() {
3018
+ const chars = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ";
3019
+ const bytes = this.getRandomBytes(4);
3020
+ let id = "";
3021
+ for (let i = 0; i < 4; i++) {
3022
+ id += chars[bytes[i] % chars.length];
3023
+ }
3024
+ const suffix = this.getRandomBytes(2);
3025
+ id += "-" + chars[suffix[0] % chars.length] + chars[suffix[1] % chars.length];
3026
+ return id;
3027
+ }
3028
+ /**
3029
+ * Generates a cryptographically strong 128-bit random secret string.
3030
+ */
3031
+ static generateSecret() {
3032
+ const bytes = this.getRandomBytes(16);
3033
+ return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
3034
+ }
3035
+ /**
3036
+ * Formats a web pairing URL where the secret is stored strictly in the URL fragment (#SECRET).
3037
+ */
3038
+ static formatPairingUrl(baseUrl, sessionId, secret) {
3039
+ const cleanBase = baseUrl.replace(/\/+$/, "");
3040
+ return `${cleanBase}/pair?s=${encodeURIComponent(sessionId)}#${secret}`;
3041
+ }
3042
+ /**
3043
+ * Formats a Capacitor mobile deep link URL.
3044
+ */
3045
+ static formatDeepLinkUrl(sessionId, secret, signalingUrl) {
3046
+ let url = `capdebug://pair?session=${encodeURIComponent(sessionId)}&secret=${encodeURIComponent(secret)}`;
3047
+ if (signalingUrl) {
3048
+ url += `&server=${encodeURIComponent(signalingUrl)}`;
3049
+ }
3050
+ return url;
3051
+ }
3052
+ /**
3053
+ * Parses a web pairing URL or Capacitor deep-link URL into session and secret components.
3054
+ */
3055
+ static parseUrl(urlStr) {
3056
+ if (!urlStr || typeof urlStr !== "string") return null;
3057
+ try {
3058
+ if (urlStr.startsWith("capdebug://")) {
3059
+ const queryIdx = urlStr.indexOf("?");
3060
+ if (queryIdx === -1) return null;
3061
+ const search = urlStr.substring(queryIdx + 1);
3062
+ const params = new URLSearchParams(search);
3063
+ const sessionId2 = params.get("session") || params.get("s");
3064
+ const secret2 = params.get("secret") || params.get("sec") || "";
3065
+ const signalingUrl2 = params.get("server") || params.get("sig") || void 0;
3066
+ if (sessionId2 && secret2) {
3067
+ return { sessionId: sessionId2, secret: secret2, signalingUrl: signalingUrl2, rawUrl: urlStr };
3068
+ }
3069
+ return null;
3070
+ }
3071
+ const url = new URL(urlStr);
3072
+ let sessionId = url.searchParams.get("s") || url.searchParams.get("session");
3073
+ if (!sessionId && url.pathname.includes("/s/")) {
3074
+ const parts = url.pathname.split("/s/");
3075
+ if (parts[1]) {
3076
+ sessionId = decodeURIComponent(parts[1].split("/")[0]);
3077
+ }
3078
+ }
3079
+ let secret = url.hash.replace(/^#/, "");
3080
+ if (!secret) {
3081
+ secret = url.searchParams.get("secret") || url.searchParams.get("sec") || "";
3082
+ }
3083
+ const signalingUrl = url.searchParams.get("server") || `${url.protocol}//${url.host}`;
3084
+ if (sessionId && secret) {
2703
3085
  return {
2704
- manufacturer: 'Web Browser',
2705
- model: nav.userAgentData?.brands?.[0]?.brand || nav.appName || 'Browser',
2706
- device: nav.platform || 'Web',
2707
- systemVersion: nav.appVersion || 'Unknown',
2708
- architecture: nav.userAgentData?.architecture || 'Unknown',
2709
- availableProcessors: nav.hardwareConcurrency || 1,
2710
- isEmulator: false,
2711
- platform: 'web',
3086
+ sessionId: sessionId.trim(),
3087
+ secret: secret.trim(),
3088
+ signalingUrl,
3089
+ rawUrl: urlStr
2712
3090
  };
3091
+ }
3092
+ return null;
3093
+ } catch {
3094
+ return null;
2713
3095
  }
2714
- async getMemoryInfo() {
2715
- const perf = typeof window !== 'undefined' ? window.performance : null;
2716
- const memory = perf && perf.memory ? perf.memory : null;
2717
- if (memory) {
2718
- return {
2719
- appMemoryUsage: memory.usedJSHeapSize,
2720
- appMemoryUsageFormatted: formatBytes(memory.usedJSHeapSize),
2721
- totalHeap: memory.totalJSHeapSize,
2722
- totalHeapFormatted: formatBytes(memory.totalJSHeapSize),
2723
- maxHeap: memory.jsHeapSizeLimit,
2724
- maxHeapFormatted: formatBytes(memory.jsHeapSizeLimit),
2725
- freeHeap: memory.totalJSHeapSize - memory.usedJSHeapSize,
2726
- freeHeapFormatted: formatBytes(memory.totalJSHeapSize - memory.usedJSHeapSize),
2727
- lowMemory: false,
2728
- };
2729
- }
2730
- return {
2731
- appMemoryUsageFormatted: 'N/A (Browser non-Chrome)',
2732
- lowMemory: false,
2733
- };
3096
+ }
3097
+ static getRandomBytes(length) {
3098
+ const bytes = new Uint8Array(length);
3099
+ if (typeof crypto !== "undefined" && crypto.getRandomValues) {
3100
+ crypto.getRandomValues(bytes);
3101
+ } else {
3102
+ for (let i = 0; i < length; i++) {
3103
+ bytes[i] = Math.floor(Math.random() * 256);
3104
+ }
3105
+ }
3106
+ return bytes;
3107
+ }
3108
+ }
3109
+
3110
+ class RemoteManager {
3111
+ transport;
3112
+ store;
3113
+ isListening = false;
3114
+ eventListener = null;
3115
+ constructor(store) {
3116
+ this.store = store;
3117
+ this.transport = new WebRTCTransport();
3118
+ this.setupControlHandler();
3119
+ }
3120
+ get activeTransport() {
3121
+ return this.transport;
3122
+ }
3123
+ async connect(config) {
3124
+ this.startEventListener();
3125
+ await this.transport.connect(config);
3126
+ }
3127
+ disconnect() {
3128
+ this.transport.disconnect();
3129
+ this.stopEventListener();
3130
+ }
3131
+ status() {
3132
+ return this.transport.getStatus();
3133
+ }
3134
+ getDiagnostics() {
3135
+ return this.transport.getDiagnostics();
3136
+ }
3137
+ async handlePairingUrl(url) {
3138
+ const parsed = PairingHelper.parseUrl(url);
3139
+ if (!parsed) {
3140
+ console.warn("[CapDebug Remote] Invalid pairing URL:", url);
3141
+ return false;
3142
+ }
3143
+ try {
3144
+ await this.connect({
3145
+ sessionId: parsed.sessionId,
3146
+ secret: parsed.secret,
3147
+ signalingUrl: parsed.signalingUrl,
3148
+ isHost: false
3149
+ });
3150
+ return true;
3151
+ } catch (err) {
3152
+ console.error("[CapDebug Remote] Failed to connect via pairing URL:", err);
3153
+ return false;
2734
3154
  }
2735
- async getWebViewInfo() {
2736
- const nav = typeof navigator !== 'undefined' ? navigator : {};
2737
- return {
2738
- userAgent: nav.userAgent || 'Unknown',
2739
- version: nav.appVersion || 'Unknown',
2740
- packageName: typeof window !== 'undefined' ? window.location.hostname : 'localhost',
2741
- debugEnabled: true,
2742
- };
3155
+ }
3156
+ startEventListener() {
3157
+ if (this.isListening || typeof window === "undefined") return;
3158
+ this.eventListener = (event) => {
3159
+ const customEvent = event;
3160
+ if (customEvent.detail) {
3161
+ this.transport.send(customEvent.detail);
3162
+ }
3163
+ };
3164
+ window.addEventListener("capdebug", this.eventListener);
3165
+ this.isListening = true;
3166
+ }
3167
+ stopEventListener() {
3168
+ if (!this.isListening || typeof window === "undefined") return;
3169
+ if (this.eventListener) {
3170
+ window.removeEventListener("capdebug", this.eventListener);
3171
+ this.eventListener = null;
2743
3172
  }
2744
- async getAppInfo() {
2745
- return {
2746
- applicationId: typeof window !== 'undefined' ? window.location.host : 'localhost',
2747
- version: '1.0.0',
2748
- versionName: '1.0.0 (Web)',
2749
- versionCode: 1,
2750
- buildType: typeof process !== 'undefined' && process.env?.NODE_ENV ? process.env.NODE_ENV : 'development',
2751
- };
3173
+ this.isListening = false;
3174
+ }
3175
+ setupControlHandler() {
3176
+ this.transport.onControl(async (cmd) => {
3177
+ switch (cmd.type) {
3178
+ case "pause":
3179
+ this.store.pause();
3180
+ break;
3181
+ case "resume":
3182
+ this.store.resume();
3183
+ break;
3184
+ case "clear":
3185
+ this.store.clear();
3186
+ break;
3187
+ case "snapshot.request":
3188
+ try {
3189
+ const [device, memory, webView, app, network] = await Promise.allSettled([
3190
+ CapDebugNative.getDeviceInfo(),
3191
+ CapDebugNative.getMemoryInfo(),
3192
+ CapDebugNative.getWebViewInfo(),
3193
+ CapDebugNative.getAppInfo(),
3194
+ CapDebugNative.getNetworkInfo()
3195
+ ]);
3196
+ const snapshot = {
3197
+ device: device.status === "fulfilled" ? device.value : null,
3198
+ memory: memory.status === "fulfilled" ? memory.value : null,
3199
+ webView: webView.status === "fulfilled" ? webView.value : null,
3200
+ app: app.status === "fulfilled" ? app.value : null,
3201
+ network: network.status === "fulfilled" ? network.value : null
3202
+ };
3203
+ this.transport.send({
3204
+ id: "diag_" + Date.now(),
3205
+ timestamp: Date.now(),
3206
+ source: "native",
3207
+ type: "device.snapshot",
3208
+ level: "info",
3209
+ message: "Live native diagnostics snapshot",
3210
+ data: snapshot
3211
+ });
3212
+ } catch (err) {
3213
+ console.error("[CapDebug Remote] Failed snapshot:", err);
3214
+ }
3215
+ break;
3216
+ case "disconnect":
3217
+ this.disconnect();
3218
+ break;
3219
+ }
3220
+ });
3221
+ }
3222
+ }
3223
+
3224
+ class DeepLinkHandler {
3225
+ static parse(url) {
3226
+ return PairingHelper.parseUrl(url);
3227
+ }
3228
+ }
3229
+
3230
+ class CapDebugManager {
3231
+ store;
3232
+ viewerElement = null;
3233
+ isMounted = false;
3234
+ remoteManager;
3235
+ customEventCollector;
3236
+ consoleCollector;
3237
+ errorCollector;
3238
+ networkCollector;
3239
+ browserEventCollector;
3240
+ bridgeCollector;
3241
+ constructor() {
3242
+ this.store = new CapDebugStore();
3243
+ this.remoteManager = new RemoteManager(this.store);
3244
+ this.customEventCollector = new CustomEventCollector(this.store);
3245
+ this.consoleCollector = new ConsoleCollector();
3246
+ this.errorCollector = new ErrorCollector();
3247
+ this.networkCollector = new NetworkCollector();
3248
+ this.browserEventCollector = new BrowserEventCollector();
3249
+ this.bridgeCollector = new BridgeCollector();
3250
+ }
3251
+ get remote() {
3252
+ return this.remoteManager;
3253
+ }
3254
+ async handlePairingUrl(url) {
3255
+ return this.remoteManager.handlePairingUrl(url);
3256
+ }
3257
+ get native() {
3258
+ return CapDebugNative;
3259
+ }
3260
+ get currentStore() {
3261
+ return this.store;
3262
+ }
3263
+ /**
3264
+ * Mounts the CapDebug UI into the DOM and activates collectors.
3265
+ */
3266
+ mount(options = {}) {
3267
+ if (typeof window === "undefined" || typeof document === "undefined") {
3268
+ return;
3269
+ }
3270
+ if (this.isMounted) {
3271
+ this.unmount();
3272
+ }
3273
+ if (options.maxEvents) {
3274
+ this.store.setMaxEvents(options.maxEvents);
3275
+ }
3276
+ if (options.customMaskKeys) {
3277
+ this.store.setCustomMaskKeys(options.customMaskKeys);
3278
+ }
3279
+ this.customEventCollector.start();
3280
+ if (options.console !== false) {
3281
+ this.consoleCollector.start();
3282
+ }
3283
+ if (options.errors !== false) {
3284
+ this.errorCollector.start();
3285
+ }
3286
+ if (options.network) {
3287
+ const netOpts = typeof options.network === "object" ? options.network : {};
3288
+ this.networkCollector.setOptions(netOpts);
3289
+ this.networkCollector.start();
3290
+ }
3291
+ if (options.bridge) {
3292
+ this.bridgeCollector.start();
3293
+ }
3294
+ this.browserEventCollector.start();
3295
+ registerCustomElement();
3296
+ this.viewerElement = document.createElement(CapDebugViewerElement.TAG);
3297
+ this.viewerElement.init(this.store, options);
3298
+ document.body.appendChild(this.viewerElement);
3299
+ this.isMounted = true;
3300
+ }
3301
+ /**
3302
+ * Unmounts CapDebug UI and stops all collectors.
3303
+ */
3304
+ unmount() {
3305
+ if (!this.isMounted) return;
3306
+ if (this.viewerElement && this.viewerElement.parentNode) {
3307
+ this.viewerElement.parentNode.removeChild(this.viewerElement);
3308
+ this.viewerElement = null;
3309
+ }
3310
+ this.customEventCollector.stop();
3311
+ this.consoleCollector.stop();
3312
+ this.errorCollector.stop();
3313
+ this.networkCollector.stop();
3314
+ this.browserEventCollector.stop();
3315
+ this.bridgeCollector.stop();
3316
+ this.isMounted = false;
3317
+ }
3318
+ /**
3319
+ * Opens the debug viewer drawer.
3320
+ */
3321
+ show() {
3322
+ if (this.viewerElement) {
3323
+ this.viewerElement.show();
2752
3324
  }
2753
- async getNetworkInfo() {
2754
- const nav = typeof navigator !== 'undefined' ? navigator : {};
2755
- const conn = nav.connection || nav.mozConnection || nav.webkitConnection;
2756
- return {
2757
- connected: nav.onLine ?? true,
2758
- connectionType: conn ? conn.effectiveType || conn.type || 'unknown' : (nav.onLine ? 'online' : 'offline'),
2759
- metered: conn ? conn.saveData ?? false : false,
2760
- };
3325
+ }
3326
+ /**
3327
+ * Closes the debug viewer drawer.
3328
+ */
3329
+ hide() {
3330
+ if (this.viewerElement) {
3331
+ this.viewerElement.hide();
2761
3332
  }
3333
+ }
3334
+ /**
3335
+ * Toggles the debug viewer drawer open/closed.
3336
+ */
3337
+ toggle() {
3338
+ if (this.viewerElement) {
3339
+ return this.viewerElement.toggle();
3340
+ }
3341
+ return false;
3342
+ }
3343
+ /**
3344
+ * Dispatches a debug event through the CustomEvent bus.
3345
+ */
3346
+ emit(event) {
3347
+ if (typeof window === "undefined") return;
3348
+ window.dispatchEvent(
3349
+ new CustomEvent("capdebug", {
3350
+ detail: {
3351
+ id: event.id,
3352
+ timestamp: event.timestamp || Date.now(),
3353
+ source: event.source || "web",
3354
+ type: event.type,
3355
+ level: event.level || "info",
3356
+ message: event.message,
3357
+ data: event.data,
3358
+ duration: event.duration,
3359
+ platform: event.platform
3360
+ }
3361
+ })
3362
+ );
3363
+ }
3364
+ /**
3365
+ * Clears all recorded events from the store.
3366
+ */
3367
+ clear() {
3368
+ this.store.clear();
3369
+ }
3370
+ /**
3371
+ * Pauses capturing new events.
3372
+ */
3373
+ pause() {
3374
+ this.store.pause();
3375
+ }
3376
+ /**
3377
+ * Resumes capturing events.
3378
+ */
3379
+ resume() {
3380
+ this.store.resume();
3381
+ }
3382
+ captureConsole() {
3383
+ this.consoleCollector.start();
3384
+ }
3385
+ releaseConsole() {
3386
+ this.consoleCollector.stop();
3387
+ }
3388
+ captureErrors() {
3389
+ this.errorCollector.start();
3390
+ }
3391
+ releaseErrors() {
3392
+ this.errorCollector.stop();
3393
+ }
3394
+ captureFetch(options) {
3395
+ if (options) {
3396
+ this.networkCollector.setOptions(options);
3397
+ }
3398
+ this.networkCollector.start();
3399
+ }
3400
+ releaseFetch() {
3401
+ this.networkCollector.stop();
3402
+ }
3403
+ observeEvent(eventName) {
3404
+ this.browserEventCollector.observe(eventName);
3405
+ }
3406
+ unobserveEvent(eventName) {
3407
+ this.browserEventCollector.unobserve(eventName);
3408
+ }
3409
+ }
3410
+ const CapDebug = new CapDebugManager();
3411
+
3412
+ function formatBytes(bytes) {
3413
+ if (bytes === 0) return "0 B";
3414
+ const k = 1024;
3415
+ const sizes = ["B", "KB", "MB", "GB"];
3416
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
3417
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
3418
+ }
3419
+ class CapDebugWeb extends core.WebPlugin {
3420
+ async getDeviceInfo() {
3421
+ const nav = typeof navigator !== "undefined" ? navigator : {};
3422
+ return {
3423
+ manufacturer: "Web Browser",
3424
+ model: nav.userAgentData?.brands?.[0]?.brand || nav.appName || "Browser",
3425
+ device: nav.platform || "Web",
3426
+ systemVersion: nav.appVersion || "Unknown",
3427
+ architecture: nav.userAgentData?.architecture || "Unknown",
3428
+ availableProcessors: nav.hardwareConcurrency || 1,
3429
+ isEmulator: false,
3430
+ platform: "web"
3431
+ };
3432
+ }
3433
+ async getMemoryInfo() {
3434
+ const perf = typeof window !== "undefined" ? window.performance : null;
3435
+ const memory = perf && perf.memory ? perf.memory : null;
3436
+ if (memory) {
3437
+ return {
3438
+ appMemoryUsage: memory.usedJSHeapSize,
3439
+ appMemoryUsageFormatted: formatBytes(memory.usedJSHeapSize),
3440
+ totalHeap: memory.totalJSHeapSize,
3441
+ totalHeapFormatted: formatBytes(memory.totalJSHeapSize),
3442
+ maxHeap: memory.jsHeapSizeLimit,
3443
+ maxHeapFormatted: formatBytes(memory.jsHeapSizeLimit),
3444
+ freeHeap: memory.totalJSHeapSize - memory.usedJSHeapSize,
3445
+ freeHeapFormatted: formatBytes(memory.totalJSHeapSize - memory.usedJSHeapSize),
3446
+ lowMemory: false
3447
+ };
3448
+ }
3449
+ return {
3450
+ appMemoryUsageFormatted: "N/A (Browser non-Chrome)",
3451
+ lowMemory: false
3452
+ };
3453
+ }
3454
+ async getWebViewInfo() {
3455
+ const nav = typeof navigator !== "undefined" ? navigator : {};
3456
+ return {
3457
+ userAgent: nav.userAgent || "Unknown",
3458
+ version: nav.appVersion || "Unknown",
3459
+ packageName: typeof window !== "undefined" ? window.location.hostname : "localhost",
3460
+ debugEnabled: true
3461
+ };
3462
+ }
3463
+ async getAppInfo() {
3464
+ return {
3465
+ applicationId: typeof window !== "undefined" ? window.location.host : "localhost",
3466
+ version: "1.0.0",
3467
+ versionName: "1.0.0 (Web)",
3468
+ versionCode: 1,
3469
+ buildType: typeof process !== "undefined" && process.env?.NODE_ENV ? process.env.NODE_ENV : "development"
3470
+ };
3471
+ }
3472
+ async getNetworkInfo() {
3473
+ const nav = typeof navigator !== "undefined" ? navigator : {};
3474
+ const conn = nav.connection || nav.mozConnection || nav.webkitConnection;
3475
+ return {
3476
+ connected: nav.onLine ?? true,
3477
+ connectionType: conn ? conn.effectiveType || conn.type || "unknown" : nav.onLine ? "online" : "offline",
3478
+ metered: conn ? conn.saveData ?? false : false
3479
+ };
3480
+ }
2762
3481
  }
2763
3482
 
2764
3483
  var web = /*#__PURE__*/Object.freeze({
2765
- __proto__: null,
2766
- CapDebugWeb: CapDebugWeb
3484
+ __proto__: null,
3485
+ CapDebugWeb: CapDebugWeb
2767
3486
  });
2768
3487
 
2769
3488
  exports.BridgeCollector = BridgeCollector;
@@ -2776,9 +3495,17 @@ exports.CapDebugViewerElement = CapDebugViewerElement;
2776
3495
  exports.ConsoleCollector = ConsoleCollector;
2777
3496
  exports.CustomEventCollector = CustomEventCollector;
2778
3497
  exports.DataSanitizer = DataSanitizer;
3498
+ exports.DeepLinkHandler = DeepLinkHandler;
2779
3499
  exports.ErrorCollector = ErrorCollector;
3500
+ exports.HttpSseSignalingClient = HttpSseSignalingClient;
2780
3501
  exports.JsonViewer = JsonViewer;
2781
3502
  exports.NetworkCollector = NetworkCollector;
3503
+ exports.PROTOCOL_VERSION = PROTOCOL_VERSION;
3504
+ exports.PairingHelper = PairingHelper;
3505
+ exports.RemoteEventBuffer = RemoteEventBuffer;
3506
+ exports.RemoteManager = RemoteManager;
3507
+ exports.RemoteProtocol = RemoteProtocol;
3508
+ exports.WebRTCTransport = WebRTCTransport;
2782
3509
  exports.default = CapDebug;
2783
3510
  exports.defaultSanitizer = defaultSanitizer;
2784
3511
  exports.registerCustomElement = registerCustomElement;