s3db.js 13.6.0 → 14.0.2

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 (193) hide show
  1. package/README.md +139 -43
  2. package/dist/s3db.cjs +72425 -38970
  3. package/dist/s3db.cjs.map +1 -1
  4. package/dist/s3db.es.js +72177 -38764
  5. package/dist/s3db.es.js.map +1 -1
  6. package/mcp/lib/base-handler.js +157 -0
  7. package/mcp/lib/handlers/connection-handler.js +280 -0
  8. package/mcp/lib/handlers/query-handler.js +533 -0
  9. package/mcp/lib/handlers/resource-handler.js +428 -0
  10. package/mcp/lib/tool-registry.js +336 -0
  11. package/mcp/lib/tools/connection-tools.js +161 -0
  12. package/mcp/lib/tools/query-tools.js +267 -0
  13. package/mcp/lib/tools/resource-tools.js +404 -0
  14. package/package.json +94 -49
  15. package/src/clients/memory-client.class.js +346 -191
  16. package/src/clients/memory-storage.class.js +300 -84
  17. package/src/clients/s3-client.class.js +7 -6
  18. package/src/concerns/geo-encoding.js +19 -2
  19. package/src/concerns/ip.js +59 -9
  20. package/src/concerns/money.js +8 -1
  21. package/src/concerns/password-hashing.js +49 -8
  22. package/src/concerns/plugin-storage.js +186 -18
  23. package/src/concerns/storage-drivers/filesystem-driver.js +284 -0
  24. package/src/database.class.js +139 -29
  25. package/src/errors.js +332 -42
  26. package/src/plugins/api/auth/oidc-auth.js +66 -17
  27. package/src/plugins/api/auth/strategies/base-strategy.class.js +74 -0
  28. package/src/plugins/api/auth/strategies/factory.class.js +63 -0
  29. package/src/plugins/api/auth/strategies/global-strategy.class.js +44 -0
  30. package/src/plugins/api/auth/strategies/path-based-strategy.class.js +83 -0
  31. package/src/plugins/api/auth/strategies/path-rules-strategy.class.js +118 -0
  32. package/src/plugins/api/concerns/failban-manager.js +106 -57
  33. package/src/plugins/api/concerns/opengraph-helper.js +116 -0
  34. package/src/plugins/api/concerns/route-context.js +601 -0
  35. package/src/plugins/api/concerns/state-machine.js +288 -0
  36. package/src/plugins/api/index.js +180 -41
  37. package/src/plugins/api/routes/auth-routes.js +198 -30
  38. package/src/plugins/api/routes/resource-routes.js +19 -4
  39. package/src/plugins/api/server/health-manager.class.js +163 -0
  40. package/src/plugins/api/server/middleware-chain.class.js +310 -0
  41. package/src/plugins/api/server/router.class.js +472 -0
  42. package/src/plugins/api/server.js +280 -1303
  43. package/src/plugins/api/utils/custom-routes.js +17 -5
  44. package/src/plugins/api/utils/guards.js +76 -17
  45. package/src/plugins/api/utils/openapi-generator-cached.class.js +133 -0
  46. package/src/plugins/api/utils/openapi-generator.js +7 -6
  47. package/src/plugins/api/utils/template-engine.js +77 -3
  48. package/src/plugins/audit.plugin.js +30 -8
  49. package/src/plugins/backup.plugin.js +110 -14
  50. package/src/plugins/cache/cache.class.js +22 -5
  51. package/src/plugins/cache/filesystem-cache.class.js +116 -19
  52. package/src/plugins/cache/memory-cache.class.js +211 -57
  53. package/src/plugins/cache/multi-tier-cache.class.js +371 -0
  54. package/src/plugins/cache/partition-aware-filesystem-cache.class.js +168 -47
  55. package/src/plugins/cache/redis-cache.class.js +552 -0
  56. package/src/plugins/cache/s3-cache.class.js +17 -8
  57. package/src/plugins/cache.plugin.js +176 -61
  58. package/src/plugins/cloud-inventory/drivers/alibaba-driver.js +8 -1
  59. package/src/plugins/cloud-inventory/drivers/aws-driver.js +60 -29
  60. package/src/plugins/cloud-inventory/drivers/azure-driver.js +8 -1
  61. package/src/plugins/cloud-inventory/drivers/base-driver.js +16 -2
  62. package/src/plugins/cloud-inventory/drivers/cloudflare-driver.js +8 -1
  63. package/src/plugins/cloud-inventory/drivers/digitalocean-driver.js +8 -1
  64. package/src/plugins/cloud-inventory/drivers/hetzner-driver.js +8 -1
  65. package/src/plugins/cloud-inventory/drivers/linode-driver.js +8 -1
  66. package/src/plugins/cloud-inventory/drivers/mongodb-atlas-driver.js +8 -1
  67. package/src/plugins/cloud-inventory/drivers/vultr-driver.js +8 -1
  68. package/src/plugins/cloud-inventory/index.js +29 -8
  69. package/src/plugins/cloud-inventory/registry.js +64 -42
  70. package/src/plugins/cloud-inventory.plugin.js +240 -138
  71. package/src/plugins/concerns/plugin-dependencies.js +54 -0
  72. package/src/plugins/concerns/resource-names.js +100 -0
  73. package/src/plugins/consumers/index.js +10 -2
  74. package/src/plugins/consumers/sqs-consumer.js +12 -2
  75. package/src/plugins/cookie-farm-suite.plugin.js +278 -0
  76. package/src/plugins/cookie-farm.errors.js +73 -0
  77. package/src/plugins/cookie-farm.plugin.js +869 -0
  78. package/src/plugins/costs.plugin.js +7 -1
  79. package/src/plugins/eventual-consistency/analytics.js +94 -19
  80. package/src/plugins/eventual-consistency/config.js +15 -7
  81. package/src/plugins/eventual-consistency/consolidation.js +29 -11
  82. package/src/plugins/eventual-consistency/garbage-collection.js +3 -1
  83. package/src/plugins/eventual-consistency/helpers.js +39 -14
  84. package/src/plugins/eventual-consistency/install.js +21 -2
  85. package/src/plugins/eventual-consistency/utils.js +32 -10
  86. package/src/plugins/fulltext.plugin.js +38 -11
  87. package/src/plugins/geo.plugin.js +61 -9
  88. package/src/plugins/identity/concerns/config.js +61 -0
  89. package/src/plugins/identity/concerns/mfa-manager.js +15 -2
  90. package/src/plugins/identity/concerns/rate-limit.js +124 -0
  91. package/src/plugins/identity/concerns/resource-schemas.js +9 -1
  92. package/src/plugins/identity/concerns/token-generator.js +29 -4
  93. package/src/plugins/identity/drivers/auth-driver.interface.js +76 -0
  94. package/src/plugins/identity/drivers/client-credentials-driver.js +127 -0
  95. package/src/plugins/identity/drivers/index.js +18 -0
  96. package/src/plugins/identity/drivers/password-driver.js +122 -0
  97. package/src/plugins/identity/email-service.js +17 -2
  98. package/src/plugins/identity/index.js +413 -69
  99. package/src/plugins/identity/oauth2-server.js +413 -30
  100. package/src/plugins/identity/oidc-discovery.js +16 -8
  101. package/src/plugins/identity/rsa-keys.js +115 -35
  102. package/src/plugins/identity/server.js +166 -45
  103. package/src/plugins/identity/session-manager.js +53 -7
  104. package/src/plugins/identity/ui/pages/mfa-verification.js +17 -15
  105. package/src/plugins/identity/ui/routes.js +363 -255
  106. package/src/plugins/importer/index.js +153 -20
  107. package/src/plugins/index.js +9 -2
  108. package/src/plugins/kubernetes-inventory/index.js +6 -0
  109. package/src/plugins/kubernetes-inventory/k8s-driver.js +867 -0
  110. package/src/plugins/kubernetes-inventory/resource-types.js +274 -0
  111. package/src/plugins/kubernetes-inventory.plugin.js +980 -0
  112. package/src/plugins/metrics.plugin.js +64 -16
  113. package/src/plugins/ml/base-model.class.js +25 -15
  114. package/src/plugins/ml/regression-model.class.js +1 -1
  115. package/src/plugins/ml.errors.js +57 -25
  116. package/src/plugins/ml.plugin.js +28 -4
  117. package/src/plugins/namespace.js +210 -0
  118. package/src/plugins/plugin.class.js +180 -8
  119. package/src/plugins/puppeteer/console-monitor.js +729 -0
  120. package/src/plugins/puppeteer/cookie-manager.js +492 -0
  121. package/src/plugins/puppeteer/network-monitor.js +816 -0
  122. package/src/plugins/puppeteer/performance-manager.js +746 -0
  123. package/src/plugins/puppeteer/proxy-manager.js +478 -0
  124. package/src/plugins/puppeteer/stealth-manager.js +556 -0
  125. package/src/plugins/puppeteer.errors.js +81 -0
  126. package/src/plugins/puppeteer.plugin.js +1327 -0
  127. package/src/plugins/queue-consumer.plugin.js +69 -14
  128. package/src/plugins/recon/behaviors/uptime-behavior.js +691 -0
  129. package/src/plugins/recon/concerns/command-runner.js +148 -0
  130. package/src/plugins/recon/concerns/diff-detector.js +372 -0
  131. package/src/plugins/recon/concerns/fingerprint-builder.js +307 -0
  132. package/src/plugins/recon/concerns/process-manager.js +338 -0
  133. package/src/plugins/recon/concerns/report-generator.js +478 -0
  134. package/src/plugins/recon/concerns/security-analyzer.js +571 -0
  135. package/src/plugins/recon/concerns/target-normalizer.js +68 -0
  136. package/src/plugins/recon/config/defaults.js +321 -0
  137. package/src/plugins/recon/config/resources.js +370 -0
  138. package/src/plugins/recon/index.js +778 -0
  139. package/src/plugins/recon/managers/dependency-manager.js +174 -0
  140. package/src/plugins/recon/managers/scheduler-manager.js +179 -0
  141. package/src/plugins/recon/managers/storage-manager.js +745 -0
  142. package/src/plugins/recon/managers/target-manager.js +274 -0
  143. package/src/plugins/recon/stages/asn-stage.js +314 -0
  144. package/src/plugins/recon/stages/certificate-stage.js +84 -0
  145. package/src/plugins/recon/stages/dns-stage.js +107 -0
  146. package/src/plugins/recon/stages/dnsdumpster-stage.js +362 -0
  147. package/src/plugins/recon/stages/fingerprint-stage.js +71 -0
  148. package/src/plugins/recon/stages/google-dorks-stage.js +440 -0
  149. package/src/plugins/recon/stages/http-stage.js +89 -0
  150. package/src/plugins/recon/stages/latency-stage.js +148 -0
  151. package/src/plugins/recon/stages/massdns-stage.js +302 -0
  152. package/src/plugins/recon/stages/osint-stage.js +1373 -0
  153. package/src/plugins/recon/stages/ports-stage.js +169 -0
  154. package/src/plugins/recon/stages/screenshot-stage.js +94 -0
  155. package/src/plugins/recon/stages/secrets-stage.js +514 -0
  156. package/src/plugins/recon/stages/subdomains-stage.js +295 -0
  157. package/src/plugins/recon/stages/tls-audit-stage.js +78 -0
  158. package/src/plugins/recon/stages/vulnerability-stage.js +78 -0
  159. package/src/plugins/recon/stages/web-discovery-stage.js +113 -0
  160. package/src/plugins/recon/stages/whois-stage.js +349 -0
  161. package/src/plugins/recon.plugin.js +75 -0
  162. package/src/plugins/recon.plugin.js.backup +2635 -0
  163. package/src/plugins/relation.errors.js +87 -14
  164. package/src/plugins/replicator.plugin.js +514 -137
  165. package/src/plugins/replicators/base-replicator.class.js +89 -1
  166. package/src/plugins/replicators/bigquery-replicator.class.js +66 -22
  167. package/src/plugins/replicators/dynamodb-replicator.class.js +22 -15
  168. package/src/plugins/replicators/mongodb-replicator.class.js +22 -15
  169. package/src/plugins/replicators/mysql-replicator.class.js +52 -17
  170. package/src/plugins/replicators/planetscale-replicator.class.js +30 -4
  171. package/src/plugins/replicators/postgres-replicator.class.js +62 -27
  172. package/src/plugins/replicators/s3db-replicator.class.js +25 -18
  173. package/src/plugins/replicators/schema-sync.helper.js +3 -3
  174. package/src/plugins/replicators/sqs-replicator.class.js +8 -2
  175. package/src/plugins/replicators/turso-replicator.class.js +23 -3
  176. package/src/plugins/replicators/webhook-replicator.class.js +42 -4
  177. package/src/plugins/s3-queue.plugin.js +464 -65
  178. package/src/plugins/scheduler.plugin.js +20 -6
  179. package/src/plugins/state-machine.plugin.js +40 -9
  180. package/src/plugins/tfstate/README.md +126 -126
  181. package/src/plugins/tfstate/base-driver.js +28 -4
  182. package/src/plugins/tfstate/errors.js +65 -10
  183. package/src/plugins/tfstate/filesystem-driver.js +52 -8
  184. package/src/plugins/tfstate/index.js +163 -90
  185. package/src/plugins/tfstate/s3-driver.js +64 -6
  186. package/src/plugins/ttl.plugin.js +72 -17
  187. package/src/plugins/vector/distances.js +18 -12
  188. package/src/plugins/vector/kmeans.js +26 -4
  189. package/src/resource.class.js +115 -19
  190. package/src/testing/factory.class.js +20 -3
  191. package/src/testing/seeder.class.js +7 -1
  192. package/src/clients/memory-client.md +0 -917
  193. package/src/plugins/cloud-inventory/drivers/mock-drivers.js +0 -449
@@ -0,0 +1,729 @@
1
+ /**
2
+ * ConsoleMonitor - Console Message Tracking
3
+ *
4
+ * Captures all console messages from the browser:
5
+ * - Errors, warnings, info, debug, logs
6
+ * - Stack traces
7
+ * - Source location (file, line, column)
8
+ * - Uncaught exceptions
9
+ * - Promise rejections
10
+ * - Performance warnings
11
+ * - Deprecation warnings
12
+ *
13
+ * Use cases:
14
+ * - JavaScript error tracking
15
+ * - Debug production issues
16
+ * - Monitor third-party script errors
17
+ * - Track console.warn/console.error usage
18
+ * - Detect performance issues
19
+ * - Security monitoring (CSP violations)
20
+ */
21
+ import tryFn from '../../concerns/try-fn.js';
22
+ import { PuppeteerError } from '../puppeteer.errors.js';
23
+
24
+ export class ConsoleMonitor {
25
+ constructor(plugin) {
26
+ this.plugin = plugin;
27
+ this.config = plugin.config.consoleMonitor || {
28
+ enabled: false,
29
+ persist: false,
30
+ filters: {
31
+ levels: null, // ['error', 'warning'] or null for all
32
+ excludePatterns: [], // Regex patterns to exclude
33
+ includeStackTraces: true,
34
+ includeSourceLocation: true,
35
+ captureNetwork: false // Also capture network errors from console
36
+ }
37
+ };
38
+
39
+ // Resources for persistence (lazy-initialized)
40
+ this.sessionsResource = null;
41
+ this.messagesResource = null;
42
+ this.errorsResource = null;
43
+
44
+ // Console message types
45
+ this.messageTypes = {
46
+ 'log': 'log',
47
+ 'debug': 'debug',
48
+ 'info': 'info',
49
+ 'error': 'error',
50
+ 'warning': 'warning',
51
+ 'warn': 'warning', // Alias
52
+ 'dir': 'dir',
53
+ 'dirxml': 'dirxml',
54
+ 'table': 'table',
55
+ 'trace': 'trace',
56
+ 'clear': 'clear',
57
+ 'startGroup': 'group',
58
+ 'startGroupCollapsed': 'groupCollapsed',
59
+ 'endGroup': 'groupEnd',
60
+ 'assert': 'assert',
61
+ 'profile': 'profile',
62
+ 'profileEnd': 'profileEnd',
63
+ 'count': 'count',
64
+ 'timeEnd': 'timeEnd',
65
+ 'verbose': 'verbose'
66
+ };
67
+ }
68
+
69
+ /**
70
+ * Initialize console monitoring resources
71
+ */
72
+ async initialize() {
73
+ if (!this.config.persist) {
74
+ return;
75
+ }
76
+
77
+ const resourceNames = this.plugin.resourceNames || {};
78
+ const sessionsName = resourceNames.consoleSessions || 'plg_puppeteer_console_sessions';
79
+ const messagesName = resourceNames.consoleMessages || 'plg_puppeteer_console_messages';
80
+ const errorsName = resourceNames.consoleErrors || 'plg_puppeteer_console_errors';
81
+
82
+ // Create sessions resource (metadata about each console session)
83
+ const [sessionsCreated, sessionsErr, sessionsResource] = await tryFn(() => this.plugin.database.createResource({
84
+ name: sessionsName,
85
+ attributes: {
86
+ sessionId: 'string|required',
87
+ url: 'string|required',
88
+ domain: 'string|required',
89
+ date: 'string|required', // YYYY-MM-DD
90
+ startTime: 'number|required',
91
+ endTime: 'number',
92
+ duration: 'number',
93
+
94
+ // Statistics
95
+ totalMessages: 'number',
96
+ errorCount: 'number',
97
+ warningCount: 'number',
98
+ logCount: 'number',
99
+ infoCount: 'number',
100
+ debugCount: 'number',
101
+
102
+ // By type breakdown
103
+ byType: 'object', // { error: 5, warning: 3, log: 20 }
104
+
105
+ // User agent
106
+ userAgent: 'string'
107
+ },
108
+ behavior: 'body-overflow',
109
+ timestamps: true,
110
+ partitions: {
111
+ byUrl: { fields: { url: 'string' } },
112
+ byDate: { fields: { date: 'string' } },
113
+ byDomain: { fields: { domain: 'string' } }
114
+ }
115
+ }));
116
+
117
+ if (sessionsCreated) {
118
+ this.sessionsResource = sessionsResource;
119
+ } else if (this.plugin.database.resources?.[sessionsName]) {
120
+ this.sessionsResource = this.plugin.database.resources[sessionsName];
121
+ } else {
122
+ throw sessionsErr;
123
+ }
124
+
125
+ // Create messages resource (all console messages)
126
+ const [messagesCreated, messagesErr, messagesResource] = await tryFn(() => this.plugin.database.createResource({
127
+ name: messagesName,
128
+ attributes: {
129
+ messageId: 'string|required',
130
+ sessionId: 'string|required',
131
+ timestamp: 'number|required',
132
+ date: 'string|required',
133
+
134
+ // Message details
135
+ type: 'string|required', // error, warning, log, info, debug, etc.
136
+ text: 'string|required',
137
+ args: 'array', // Console.log arguments
138
+
139
+ // Source location
140
+ source: 'object', // { url, lineNumber, columnNumber }
141
+
142
+ // Stack trace (for errors)
143
+ stackTrace: 'object',
144
+
145
+ // Context
146
+ url: 'string', // Page URL when message occurred
147
+ domain: 'string'
148
+ },
149
+ behavior: 'body-overflow',
150
+ timestamps: true,
151
+ partitions: {
152
+ bySession: { fields: { sessionId: 'string' } },
153
+ byType: { fields: { type: 'string' } },
154
+ byDate: { fields: { date: 'string' } },
155
+ byDomain: { fields: { domain: 'string' } }
156
+ }
157
+ }));
158
+
159
+ if (messagesCreated) {
160
+ this.messagesResource = messagesResource;
161
+ } else if (this.plugin.database.resources?.[messagesName]) {
162
+ this.messagesResource = this.plugin.database.resources[messagesName];
163
+ } else {
164
+ throw messagesErr;
165
+ }
166
+
167
+ // Create errors resource (errors and exceptions only)
168
+ const [errorsCreated, errorsErr, errorsResource] = await tryFn(() => this.plugin.database.createResource({
169
+ name: errorsName,
170
+ attributes: {
171
+ errorId: 'string|required',
172
+ sessionId: 'string|required',
173
+ messageId: 'string|required',
174
+ timestamp: 'number|required',
175
+ date: 'string|required',
176
+
177
+ // Error details
178
+ errorType: 'string', // TypeError, ReferenceError, etc.
179
+ message: 'string|required',
180
+ stackTrace: 'object',
181
+
182
+ // Source location
183
+ url: 'string', // Script URL
184
+ lineNumber: 'number',
185
+ columnNumber: 'number',
186
+
187
+ // Context
188
+ pageUrl: 'string', // Page URL when error occurred
189
+ domain: 'string',
190
+
191
+ // Classification
192
+ isUncaught: 'boolean',
193
+ isPromiseRejection: 'boolean',
194
+ isNetworkError: 'boolean',
195
+ isSyntaxError: 'boolean'
196
+ },
197
+ behavior: 'body-overflow',
198
+ timestamps: true,
199
+ partitions: {
200
+ bySession: { fields: { sessionId: 'string' } },
201
+ byErrorType: { fields: { errorType: 'string' } },
202
+ byDate: { fields: { date: 'string' } },
203
+ byDomain: { fields: { domain: 'string' } }
204
+ }
205
+ }));
206
+
207
+ if (errorsCreated) {
208
+ this.errorsResource = errorsResource;
209
+ } else if (this.plugin.database.resources?.[errorsName]) {
210
+ this.errorsResource = this.plugin.database.resources[errorsName];
211
+ } else {
212
+ throw errorsErr;
213
+ }
214
+
215
+ this.plugin.emit('consoleMonitor.initialized', {
216
+ persist: this.config.persist
217
+ });
218
+ }
219
+
220
+ /**
221
+ * Start monitoring console messages for a page
222
+ * @param {Page} page - Puppeteer page
223
+ * @param {Object} options - Monitoring options
224
+ * @returns {Object} Session object
225
+ */
226
+ async startMonitoring(page, options = {}) {
227
+ const {
228
+ sessionId = `console_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
229
+ persist = this.config.persist,
230
+ filters = this.config.filters
231
+ } = options;
232
+
233
+ const session = {
234
+ sessionId,
235
+ url: page.url(),
236
+ domain: this._extractDomain(page.url()),
237
+ date: new Date().toISOString().split('T')[0],
238
+ startTime: Date.now(),
239
+ endTime: null,
240
+ duration: null,
241
+
242
+ // Tracked data
243
+ messages: [],
244
+ errors: [],
245
+ exceptions: [],
246
+ promiseRejections: [],
247
+
248
+ // Statistics
249
+ stats: {
250
+ totalMessages: 0,
251
+ errorCount: 0,
252
+ warningCount: 0,
253
+ logCount: 0,
254
+ infoCount: 0,
255
+ debugCount: 0,
256
+ byType: {}
257
+ }
258
+ };
259
+
260
+ // Console message handler
261
+ const consoleHandler = (msg) => {
262
+ const type = this.messageTypes[msg.type()] || msg.type();
263
+
264
+ // Apply filters
265
+ if (filters.levels && !filters.levels.includes(type)) {
266
+ return;
267
+ }
268
+
269
+ const text = msg.text();
270
+
271
+ // Exclude patterns
272
+ if (filters.excludePatterns && filters.excludePatterns.length > 0) {
273
+ for (const pattern of filters.excludePatterns) {
274
+ if (new RegExp(pattern).test(text)) {
275
+ return;
276
+ }
277
+ }
278
+ }
279
+
280
+ const message = {
281
+ messageId: `msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
282
+ timestamp: Date.now(),
283
+ type,
284
+ text,
285
+ args: msg.args().length > 0 ? msg.args().map(arg => this._serializeArg(arg)) : [],
286
+ url: page.url()
287
+ };
288
+
289
+ // Get source location if enabled
290
+ if (filters.includeSourceLocation && msg.location()) {
291
+ message.source = {
292
+ url: msg.location().url,
293
+ lineNumber: msg.location().lineNumber,
294
+ columnNumber: msg.location().columnNumber
295
+ };
296
+ }
297
+
298
+ // Get stack trace for errors if enabled
299
+ if (filters.includeStackTraces && (type === 'error' || type === 'warning')) {
300
+ message.stackTrace = msg.stackTrace();
301
+ }
302
+
303
+ session.messages.push(message);
304
+ session.stats.totalMessages++;
305
+
306
+ // Update type counters
307
+ if (!session.stats.byType[type]) {
308
+ session.stats.byType[type] = 0;
309
+ }
310
+ session.stats.byType[type]++;
311
+
312
+ // Update specific counters
313
+ switch (type) {
314
+ case 'error':
315
+ session.stats.errorCount++;
316
+ session.errors.push(message);
317
+ break;
318
+ case 'warning':
319
+ session.stats.warningCount++;
320
+ break;
321
+ case 'log':
322
+ session.stats.logCount++;
323
+ break;
324
+ case 'info':
325
+ session.stats.infoCount++;
326
+ break;
327
+ case 'debug':
328
+ session.stats.debugCount++;
329
+ break;
330
+ }
331
+ };
332
+
333
+ // Page error handler (uncaught exceptions)
334
+ const pageErrorHandler = (error) => {
335
+ const errorData = {
336
+ errorId: `error_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
337
+ timestamp: Date.now(),
338
+ errorType: error.name || 'Error',
339
+ message: error.message,
340
+ stack: error.stack,
341
+ isUncaught: true,
342
+ url: page.url()
343
+ };
344
+
345
+ session.exceptions.push(errorData);
346
+ session.stats.errorCount++;
347
+
348
+ // Also add to messages for unified view
349
+ session.messages.push({
350
+ messageId: errorData.errorId,
351
+ timestamp: errorData.timestamp,
352
+ type: 'error',
353
+ text: `Uncaught: ${error.message}`,
354
+ stackTrace: error.stack,
355
+ url: page.url()
356
+ });
357
+ };
358
+
359
+ // Promise rejection handler
360
+ const pageerrorHandler = (error) => {
361
+ const errorData = {
362
+ errorId: `rejection_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
363
+ timestamp: Date.now(),
364
+ errorType: 'UnhandledPromiseRejection',
365
+ message: error.message || String(error),
366
+ stack: error.stack,
367
+ isPromiseRejection: true,
368
+ url: page.url()
369
+ };
370
+
371
+ session.promiseRejections.push(errorData);
372
+ session.stats.errorCount++;
373
+
374
+ // Also add to messages
375
+ session.messages.push({
376
+ messageId: errorData.errorId,
377
+ timestamp: errorData.timestamp,
378
+ type: 'error',
379
+ text: `Unhandled Promise Rejection: ${errorData.message}`,
380
+ stackTrace: error.stack,
381
+ url: page.url()
382
+ });
383
+ };
384
+
385
+ // Attach handlers
386
+ page.on('console', consoleHandler);
387
+ page.on('pageerror', pageErrorHandler);
388
+ page.on('pageerror', pageerrorHandler); // Some versions use different event
389
+
390
+ // Store handlers for cleanup
391
+ session._handlers = {
392
+ console: consoleHandler,
393
+ pageerror: pageErrorHandler,
394
+ pageerror2: pageerrorHandler
395
+ };
396
+ session._page = page;
397
+ session._persist = persist;
398
+
399
+ this.plugin.emit('consoleMonitor.sessionStarted', {
400
+ sessionId,
401
+ url: page.url()
402
+ });
403
+
404
+ return session;
405
+ }
406
+
407
+ /**
408
+ * Stop monitoring and optionally persist data
409
+ * @param {Object} session - Session object from startMonitoring
410
+ * @param {Object} options - Stop options
411
+ * @returns {Object} Final session data
412
+ */
413
+ async stopMonitoring(session, options = {}) {
414
+ const { persist = session._persist } = options;
415
+
416
+ session.endTime = Date.now();
417
+ session.duration = session.endTime - session.startTime;
418
+
419
+ // Remove handlers
420
+ if (session._page && session._handlers) {
421
+ session._page.off('console', session._handlers.console);
422
+ session._page.off('pageerror', session._handlers.pageerror);
423
+ session._page.off('pageerror', session._handlers.pageerror2);
424
+ }
425
+
426
+ // Persist to S3DB if enabled
427
+ if (persist && this.sessionsResource) {
428
+ try {
429
+ await this._persistSession(session);
430
+ } catch (err) {
431
+ this.plugin.emit('consoleMonitor.persistFailed', {
432
+ sessionId: session.sessionId,
433
+ error: err.message
434
+ });
435
+ }
436
+ }
437
+
438
+ this.plugin.emit('consoleMonitor.sessionStopped', {
439
+ sessionId: session.sessionId,
440
+ duration: session.duration,
441
+ totalMessages: session.stats.totalMessages,
442
+ errorCount: session.stats.errorCount
443
+ });
444
+
445
+ // Clean up references
446
+ delete session._handlers;
447
+ delete session._page;
448
+
449
+ return session;
450
+ }
451
+
452
+ /**
453
+ * Persist session data to S3DB
454
+ * @private
455
+ */
456
+ async _persistSession(session) {
457
+ const startPersist = Date.now();
458
+
459
+ // Save session metadata
460
+ await this.sessionsResource.insert({
461
+ sessionId: session.sessionId,
462
+ url: session.url,
463
+ domain: session.domain,
464
+ date: session.date,
465
+ startTime: session.startTime,
466
+ endTime: session.endTime,
467
+ duration: session.duration,
468
+ totalMessages: session.stats.totalMessages,
469
+ errorCount: session.stats.errorCount,
470
+ warningCount: session.stats.warningCount,
471
+ logCount: session.stats.logCount,
472
+ infoCount: session.stats.infoCount,
473
+ debugCount: session.stats.debugCount,
474
+ byType: session.stats.byType,
475
+ userAgent: session._page?._userAgent || null
476
+ });
477
+
478
+ // Save messages (batch)
479
+ if (session.messages.length > 0) {
480
+ for (const msg of session.messages) {
481
+ await this.messagesResource.insert({
482
+ messageId: msg.messageId,
483
+ sessionId: session.sessionId,
484
+ timestamp: msg.timestamp,
485
+ date: session.date,
486
+ type: msg.type,
487
+ text: msg.text,
488
+ args: msg.args,
489
+ source: msg.source || null,
490
+ stackTrace: msg.stackTrace || null,
491
+ url: msg.url,
492
+ domain: this._extractDomain(msg.url)
493
+ });
494
+ }
495
+ }
496
+
497
+ // Save errors separately (uncaught exceptions + promise rejections)
498
+ const allErrors = [
499
+ ...session.exceptions.map(e => ({ ...e, isUncaught: true })),
500
+ ...session.promiseRejections.map(e => ({ ...e, isPromiseRejection: true }))
501
+ ];
502
+
503
+ if (allErrors.length > 0) {
504
+ for (const error of allErrors) {
505
+ // Parse error type from message or use generic
506
+ const errorType = this._extractErrorType(error.message);
507
+
508
+ // Parse source location from stack if available
509
+ const sourceLocation = this._parseStackTrace(error.stack);
510
+
511
+ await this.errorsResource.insert({
512
+ errorId: error.errorId,
513
+ sessionId: session.sessionId,
514
+ messageId: error.errorId,
515
+ timestamp: error.timestamp,
516
+ date: session.date,
517
+ errorType: error.errorType || errorType,
518
+ message: error.message,
519
+ stackTrace: this._formatStackTrace(error.stack),
520
+ url: sourceLocation?.url || null,
521
+ lineNumber: sourceLocation?.lineNumber || null,
522
+ columnNumber: sourceLocation?.columnNumber || null,
523
+ pageUrl: error.url,
524
+ domain: this._extractDomain(error.url),
525
+ isUncaught: error.isUncaught || false,
526
+ isPromiseRejection: error.isPromiseRejection || false,
527
+ isNetworkError: this._isNetworkError(error.message),
528
+ isSyntaxError: errorType === 'SyntaxError'
529
+ });
530
+ }
531
+ }
532
+
533
+ const persistDuration = Date.now() - startPersist;
534
+
535
+ this.plugin.emit('consoleMonitor.persisted', {
536
+ sessionId: session.sessionId,
537
+ messages: session.messages.length,
538
+ errors: allErrors.length,
539
+ duration: persistDuration
540
+ });
541
+ }
542
+
543
+ /**
544
+ * Extract domain from URL
545
+ * @private
546
+ */
547
+ _extractDomain(url) {
548
+ try {
549
+ return new URL(url).hostname;
550
+ } catch {
551
+ return 'unknown';
552
+ }
553
+ }
554
+
555
+ /**
556
+ * Serialize console.log argument
557
+ * @private
558
+ */
559
+ _serializeArg(arg) {
560
+ try {
561
+ return arg.toString();
562
+ } catch {
563
+ return '[Object]';
564
+ }
565
+ }
566
+
567
+ /**
568
+ * Extract error type from message
569
+ * @private
570
+ */
571
+ _extractErrorType(message) {
572
+ const errorTypes = [
573
+ 'TypeError',
574
+ 'ReferenceError',
575
+ 'SyntaxError',
576
+ 'RangeError',
577
+ 'URIError',
578
+ 'EvalError',
579
+ 'SecurityError',
580
+ 'NetworkError'
581
+ ];
582
+
583
+ for (const type of errorTypes) {
584
+ if (message.includes(type)) {
585
+ return type;
586
+ }
587
+ }
588
+
589
+ return 'Error';
590
+ }
591
+
592
+ /**
593
+ * Parse stack trace to extract source location
594
+ * @private
595
+ */
596
+ _parseStackTrace(stack) {
597
+ if (!stack) return null;
598
+
599
+ try {
600
+ // Parse first line of stack trace
601
+ const lines = stack.split('\n');
602
+ const firstLine = lines[0] || '';
603
+
604
+ // Match various stack trace formats
605
+ const match = firstLine.match(/at\s+(.+?):(\d+):(\d+)/) ||
606
+ firstLine.match(/(.+?):(\d+):(\d+)/) ||
607
+ firstLine.match(/@(.+?):(\d+):(\d+)/);
608
+
609
+ if (match) {
610
+ return {
611
+ url: match[1],
612
+ lineNumber: parseInt(match[2], 10),
613
+ columnNumber: parseInt(match[3], 10)
614
+ };
615
+ }
616
+ } catch {
617
+ // Ignore parse errors
618
+ }
619
+
620
+ return null;
621
+ }
622
+
623
+ /**
624
+ * Format stack trace for storage
625
+ * @private
626
+ */
627
+ _formatStackTrace(stack) {
628
+ if (!stack) return null;
629
+
630
+ try {
631
+ const lines = stack.split('\n').slice(0, 10); // Keep first 10 lines
632
+ return {
633
+ raw: stack.substring(0, 2000), // Limit to 2KB
634
+ frames: lines.map(line => line.trim())
635
+ };
636
+ } catch {
637
+ return { raw: stack.substring(0, 2000) };
638
+ }
639
+ }
640
+
641
+ /**
642
+ * Check if error is network-related
643
+ * @private
644
+ */
645
+ _isNetworkError(message) {
646
+ const networkKeywords = [
647
+ 'net::ERR_',
648
+ 'NetworkError',
649
+ 'Failed to fetch',
650
+ 'fetch failed',
651
+ 'XMLHttpRequest',
652
+ 'CORS',
653
+ 'Network request failed'
654
+ ];
655
+
656
+ return networkKeywords.some(keyword => message.includes(keyword));
657
+ }
658
+
659
+ /**
660
+ * Get session statistics
661
+ * @param {string} sessionId - Session ID
662
+ * @returns {Promise<Object>} Statistics
663
+ */
664
+ async getSessionStats(sessionId) {
665
+ if (!this.sessionsResource) {
666
+ throw new PuppeteerError('Console monitoring persistence not enabled', {
667
+ operation: 'getSessionStats',
668
+ retriable: false,
669
+ suggestion: 'Enable persistence in ConsoleMonitor configuration to query stored sessions.'
670
+ });
671
+ }
672
+
673
+ return await this.sessionsResource.get(sessionId);
674
+ }
675
+
676
+ /**
677
+ * Query messages for a session
678
+ * @param {string} sessionId - Session ID
679
+ * @param {Object} filters - Query filters
680
+ * @returns {Promise<Array>} Messages
681
+ */
682
+ async getSessionMessages(sessionId, filters = {}) {
683
+ if (!this.messagesResource) {
684
+ throw new PuppeteerError('Console monitoring persistence not enabled', {
685
+ operation: 'getSessionMessages',
686
+ retriable: false,
687
+ suggestion: 'Enable persistence in ConsoleMonitor configuration to query stored messages.'
688
+ });
689
+ }
690
+
691
+ return await this.messagesResource.listPartition('bySession', { sessionId }, filters);
692
+ }
693
+
694
+ /**
695
+ * Query errors for a session
696
+ * @param {string} sessionId - Session ID
697
+ * @returns {Promise<Array>} Errors
698
+ */
699
+ async getSessionErrors(sessionId) {
700
+ if (!this.errorsResource) {
701
+ throw new PuppeteerError('Console monitoring persistence not enabled', {
702
+ operation: 'getSessionErrors',
703
+ retriable: false,
704
+ suggestion: 'Enable persistence in ConsoleMonitor configuration to query stored errors.'
705
+ });
706
+ }
707
+
708
+ return await this.errorsResource.listPartition('bySession', { sessionId });
709
+ }
710
+
711
+ /**
712
+ * Query all errors by type
713
+ * @param {string} errorType - Error type
714
+ * @returns {Promise<Array>} Errors
715
+ */
716
+ async getErrorsByType(errorType) {
717
+ if (!this.errorsResource) {
718
+ throw new PuppeteerError('Console monitoring persistence not enabled', {
719
+ operation: 'getErrorsByType',
720
+ retriable: false,
721
+ suggestion: 'Enable persistence in ConsoleMonitor configuration to query stored errors.'
722
+ });
723
+ }
724
+
725
+ return await this.errorsResource.listPartition('byErrorType', { errorType });
726
+ }
727
+ }
728
+
729
+ export default ConsoleMonitor;