server-devtools 0.1.5 → 0.1.8

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 (102) hide show
  1. package/README.md +150 -6
  2. package/dist/dashboard/assets/alert-dialog-DOm6Ua9Q.js +1 -0
  3. package/dist/dashboard/assets/button-CXALLF_v.js +1 -0
  4. package/dist/dashboard/assets/index-9FEJIlQV.css +2 -0
  5. package/dist/dashboard/assets/index-B9G80X8M.js +10 -0
  6. package/dist/dashboard/assets/overview-page-CZhNooRx.js +53 -0
  7. package/dist/dashboard/assets/requests-page-DvONd75O.js +1 -0
  8. package/dist/dashboard/assets/rolldown-runtime-hePW80VL.js +1 -0
  9. package/dist/dashboard/assets/trace-details-page-Dze7uRsF.js +1 -0
  10. package/dist/dashboard/assets/with-selector-B8m4Ky1i.js +1 -0
  11. package/dist/dashboard/index.html +22 -2
  12. package/dist/src/config/index.d.ts +16 -0
  13. package/dist/src/config/index.d.ts.map +1 -1
  14. package/dist/src/config/index.js +31 -2
  15. package/dist/src/config/index.js.map +1 -1
  16. package/dist/src/core/repeated-database-queries/index.d.ts +32 -0
  17. package/dist/src/core/repeated-database-queries/index.d.ts.map +1 -0
  18. package/dist/src/core/repeated-database-queries/index.js +130 -0
  19. package/dist/src/core/repeated-database-queries/index.js.map +1 -0
  20. package/dist/src/core/repeated-database-queries/mongodb.d.ts +2 -0
  21. package/dist/src/core/repeated-database-queries/mongodb.d.ts.map +1 -0
  22. package/dist/src/core/repeated-database-queries/mongodb.js +43 -0
  23. package/dist/src/core/repeated-database-queries/mongodb.js.map +1 -0
  24. package/dist/src/core/repeated-database-queries/mysql.d.ts +2 -0
  25. package/dist/src/core/repeated-database-queries/mysql.d.ts.map +1 -0
  26. package/dist/src/core/repeated-database-queries/mysql.js +16 -0
  27. package/dist/src/core/repeated-database-queries/mysql.js.map +1 -0
  28. package/dist/src/core/repeated-database-queries/postgres.d.ts +2 -0
  29. package/dist/src/core/repeated-database-queries/postgres.d.ts.map +1 -0
  30. package/dist/src/core/repeated-database-queries/postgres.js +15 -0
  31. package/dist/src/core/repeated-database-queries/postgres.js.map +1 -0
  32. package/dist/src/core/repeated-database-queries/redis.d.ts +8 -0
  33. package/dist/src/core/repeated-database-queries/redis.d.ts.map +1 -0
  34. package/dist/src/core/repeated-database-queries/redis.js +91 -0
  35. package/dist/src/core/repeated-database-queries/redis.js.map +1 -0
  36. package/dist/src/core/repeated-database-queries/sql-template.d.ts +2 -0
  37. package/dist/src/core/repeated-database-queries/sql-template.d.ts.map +1 -0
  38. package/dist/src/core/repeated-database-queries/sql-template.js +12 -0
  39. package/dist/src/core/repeated-database-queries/sql-template.js.map +1 -0
  40. package/dist/src/core/slow-span.d.ts +5 -0
  41. package/dist/src/core/slow-span.d.ts.map +1 -0
  42. package/dist/src/core/slow-span.js +37 -0
  43. package/dist/src/core/slow-span.js.map +1 -0
  44. package/dist/src/dashboard/dashboard-server.d.ts +12 -1
  45. package/dist/src/dashboard/dashboard-server.d.ts.map +1 -1
  46. package/dist/src/dashboard/dashboard-server.js +344 -27
  47. package/dist/src/dashboard/dashboard-server.js.map +1 -1
  48. package/dist/src/index.d.ts +2 -1
  49. package/dist/src/index.d.ts.map +1 -1
  50. package/dist/src/index.js +35 -5
  51. package/dist/src/index.js.map +1 -1
  52. package/dist/src/instrumentation/exporter.d.ts +2 -1
  53. package/dist/src/instrumentation/exporter.d.ts.map +1 -1
  54. package/dist/src/instrumentation/exporter.js +3 -2
  55. package/dist/src/instrumentation/exporter.js.map +1 -1
  56. package/dist/src/instrumentation/instrumentation.d.ts +2 -1
  57. package/dist/src/instrumentation/instrumentation.d.ts.map +1 -1
  58. package/dist/src/instrumentation/instrumentation.js +4 -3
  59. package/dist/src/instrumentation/instrumentation.js.map +1 -1
  60. package/dist/src/instrumentation/outbound-http/outbound-http-capture.d.ts +8 -1
  61. package/dist/src/instrumentation/outbound-http/outbound-http-capture.d.ts.map +1 -1
  62. package/dist/src/instrumentation/outbound-http/outbound-http-capture.js +158 -32
  63. package/dist/src/instrumentation/outbound-http/outbound-http-capture.js.map +1 -1
  64. package/dist/src/instrumentation/span-mapper.d.ts +4 -0
  65. package/dist/src/instrumentation/span-mapper.d.ts.map +1 -1
  66. package/dist/src/instrumentation/span-mapper.js +25 -3
  67. package/dist/src/instrumentation/span-mapper.js.map +1 -1
  68. package/dist/src/security/auth.service.d.ts +6 -0
  69. package/dist/src/security/auth.service.d.ts.map +1 -1
  70. package/dist/src/security/auth.service.js +62 -4
  71. package/dist/src/security/auth.service.js.map +1 -1
  72. package/dist/src/security/redaction.service.d.ts +8 -0
  73. package/dist/src/security/redaction.service.d.ts.map +1 -0
  74. package/dist/src/security/redaction.service.js +35 -0
  75. package/dist/src/security/redaction.service.js.map +1 -0
  76. package/dist/src/storage/sqlite-storage.d.ts +11 -2
  77. package/dist/src/storage/sqlite-storage.d.ts.map +1 -1
  78. package/dist/src/storage/sqlite-storage.js +194 -13
  79. package/dist/src/storage/sqlite-storage.js.map +1 -1
  80. package/dist/src/types/dashboard.d.ts +25 -0
  81. package/dist/src/types/dashboard.d.ts.map +1 -1
  82. package/dist/src/types/index.d.ts +1 -0
  83. package/dist/src/types/index.d.ts.map +1 -1
  84. package/dist/src/types/index.js +1 -0
  85. package/dist/src/types/index.js.map +1 -1
  86. package/dist/src/types/misc.d.ts +5 -0
  87. package/dist/src/types/misc.d.ts.map +1 -1
  88. package/dist/src/types/outbound-http.d.ts +9 -0
  89. package/dist/src/types/outbound-http.d.ts.map +1 -0
  90. package/dist/src/types/outbound-http.js +3 -0
  91. package/dist/src/types/outbound-http.js.map +1 -0
  92. package/dist/src/utils/allowed-ips.d.ts +4 -0
  93. package/dist/src/utils/allowed-ips.d.ts.map +1 -0
  94. package/dist/src/utils/allowed-ips.js +34 -0
  95. package/dist/src/utils/allowed-ips.js.map +1 -0
  96. package/dist/src/utils/helpers.d.ts +2 -0
  97. package/dist/src/utils/helpers.d.ts.map +1 -0
  98. package/dist/src/utils/helpers.js +13 -0
  99. package/dist/src/utils/helpers.js.map +1 -0
  100. package/package.json +3 -2
  101. package/dist/dashboard/assets/index-BXbomG5W.js +0 -9
  102. package/dist/dashboard/assets/index-DYvJ5RTl.css +0 -2
@@ -2,6 +2,10 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const api_1 = require("@opentelemetry/api");
4
4
  class SpanMapper {
5
+ redactionService;
6
+ constructor(redactionService) {
7
+ this.redactionService = redactionService;
8
+ }
5
9
  map(span) {
6
10
  const context = span.spanContext();
7
11
  const exceptionEvent = span?.events?.find((event) => event.name === 'exception');
@@ -13,9 +17,7 @@ class SpanMapper {
13
17
  name: span.name,
14
18
  startedAt: this.hrTimeToMilliseconds(span.startTime),
15
19
  durationMs: this.hrTimeToMilliseconds(span.duration),
16
- attributes: {
17
- ...span.attributes,
18
- },
20
+ attributes: this.redactHeaderAttributes(span.attributes),
19
21
  status: {
20
22
  code: span.status.code,
21
23
  message: span.status.message,
@@ -29,6 +31,26 @@ class SpanMapper {
29
31
  : undefined,
30
32
  };
31
33
  }
34
+ redactHeaderAttributes(attributes) {
35
+ const redactedAttributes = { ...attributes };
36
+ const headerPrefixes = [
37
+ "http.request.header.",
38
+ "http.response.header.",
39
+ ];
40
+ for (const [key, value] of Object.entries(attributes)) {
41
+ const normalizedKey = key.toLowerCase();
42
+ const prefix = headerPrefixes.find((candidate) => normalizedKey.startsWith(candidate));
43
+ if (!prefix) {
44
+ continue;
45
+ }
46
+ const headerName = key.slice(prefix.length);
47
+ const redactedHeader = this.redactionService.redactData({
48
+ [headerName]: value,
49
+ });
50
+ redactedAttributes[key] = redactedHeader[headerName];
51
+ }
52
+ return redactedAttributes;
53
+ }
32
54
  getType(span) {
33
55
  if (span.attributes['db.system.name']) {
34
56
  return 'database';
@@ -1 +1 @@
1
- {"version":3,"file":"span-mapper.js","sourceRoot":"","sources":["../../../src/instrumentation/span-mapper.ts"],"names":[],"mappings":";;AAEA,4CAA8C;AAG9C;IAEI,GAAG,CAAC,IAAkB;QAClB,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;QAEnC,MAAM,cAAc,GAAG,IAAI,EAAE,MAAM,EAAE,IAAI,CACrC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,WAAW,CACxC,CAAC;QAEF,OAAO;YACH,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,YAAY,EAAE,IAAI,CAAC,iBAAiB,EAAE,MAAM;YAE5C,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC;YAExB,IAAI,EAAE,IAAI,CAAC,IAAI;YAEf,SAAS,EAAE,IAAI,CAAC,oBAAoB,CAAC,IAAI,CAAC,SAAS,CAAC;YACpD,UAAU,EAAE,IAAI,CAAC,oBAAoB,CAAC,IAAI,CAAC,QAAQ,CAAC;YAEpD,UAAU,EAAE;gBACR,GAAG,IAAI,CAAC,UAAU;aACrB;YAED,MAAM,EAAE;gBACJ,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI;gBACtB,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,OAAO;aAC/B;YACD,KAAK,EAAE,cAAc;gBACjB,CAAC,CAAC;oBACE,IAAI,EACA,cAAc,CAAC,UAAU,EAAE,CAAC,gBAAgB,CAAC,EAAE,QAAQ,EAAE;oBAC7D,OAAO,EACH,cAAc,CAAC,UAAU,EAAE,CAAC,mBAAmB,CAAC,EAAE,QAAQ,EAAE;oBAChE,KAAK,EACD,cAAc,CAAC,UAAU,EAAE,CAAC,sBAAsB,CAAC,EAAE,QAAQ,EAAE;iBACtE;gBACD,CAAC,CAAC,SAAS;SAClB,CAAA;IACL,CAAC;IAEO,OAAO,CAAC,IAAkB;QAC9B,IAAI,IAAI,CAAC,UAAU,CAAC,gBAAgB,CAAC,EAAE,CAAC;YACpC,OAAO,UAAU,CAAC;QACtB,CAAC;QAED,IAAI,IAAI,CAAC,UAAU,CAAC,cAAc,CAAC,EAAE,CAAC;YAClC,OAAO,WAAW,CAAC;QACvB,CAAC;QAED,QAAQ,IAAI,CAAC,IAAI,EAAE,CAAC;YAChB,KAAK,cAAQ,CAAC,MAAM;gBAChB,OAAO,aAAa,CAAC;YAEzB,KAAK,cAAQ,CAAC,MAAM;gBAChB,OAAO,aAAa,CAAC;YAEzB;gBACI,OAAO,SAAS,CAAC;QACzB,CAAC;IACL,CAAC;IAEO,oBAAoB,CAAC,IAAsB;QAC/C,MAAM,CAAC,OAAO,EAAE,WAAW,CAAC,GAAG,IAAI,CAAC;QAEpC,OAAO,OAAO,GAAG,IAAI,GAAG,WAAW,GAAG,SAAS,CAAC;IACpD,CAAC;CAIJ"}
1
+ {"version":3,"file":"span-mapper.js","sourceRoot":"","sources":["../../../src/instrumentation/span-mapper.ts"],"names":[],"mappings":"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"}
@@ -4,7 +4,13 @@ declare class AuthService {
4
4
  private readonly username;
5
5
  private readonly password;
6
6
  private readonly storage;
7
+ private readonly sessionKey;
8
+ private verifier;
7
9
  constructor(username: string, password: string, storage: SQLiteStorage);
10
+ private initializeSessions;
11
+ private deriveCredentialHash;
12
+ private credentialsMatch;
13
+ private reconcileCredentialVerifier;
8
14
  private generateSessionToken;
9
15
  private hashSessionToken;
10
16
  validateCredentials(username: string, password: string): boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"auth.service.d.ts","sourceRoot":"","sources":["../../../src/security/auth.service.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,MAAM,2BAA2B,CAAC;AACtD,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAIpC,cAAM,WAAW;IAET,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAH5B,YACqB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,aAAa,EAG1C;IAED,OAAO,CAAC,oBAAoB;IAI5B,OAAO,CAAC,gBAAgB;IAQxB,mBAAmB,CACf,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,GACjB,OAAO,CAMT;IAGD,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAwBtC;IAED,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CA4B9C;IAED,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CASjC;CACJ;eAGc,WAAW"}
1
+ {"version":3,"file":"auth.service.d.ts","sourceRoot":"","sources":["../../../src/security/auth.service.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,MAAM,2BAA2B,CAAC;AACtD,OAAO,EAA2B,QAAQ,EAAE,MAAM,UAAU,CAAC;AAI7D,cAAM,WAAW;IAOT,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAN5B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAA2B;IAE3C,YACqB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,aAAa,EAa1C;IAED,OAAO,CAAC,kBAAkB;IAY1B,OAAO,CAAC,oBAAoB;IAY5B,OAAO,CAAC,gBAAgB;IAUxB,OAAO,CAAC,2BAA2B;IAenC,OAAO,CAAC,oBAAoB;IAI5B,OAAO,CAAC,gBAAgB;IAQxB,mBAAmB,CACf,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,GACjB,OAAO,CAUT;IAGD,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAkCtC;IAED,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CA6B9C;IAED,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CASjC;CACJ;eAGc,WAAW"}
@@ -9,23 +9,76 @@ class AuthService {
9
9
  username;
10
10
  password;
11
11
  storage;
12
+ // Session hashes are keyed by the configured credentials so that changing
13
+ // the username or password invalidates every previously issued session.
14
+ sessionKey;
15
+ verifier;
12
16
  constructor(username, password, storage) {
13
17
  this.username = username;
14
18
  this.password = password;
15
19
  this.storage = storage;
16
20
  this.username = this.username ? this.username?.toLowerCase()?.trim() : '';
21
+ this.sessionKey = node_crypto_1.default
22
+ .createHash("sha256")
23
+ .update(JSON.stringify([
24
+ "server-devtools-session-v1",
25
+ this.username,
26
+ this.password,
27
+ ]))
28
+ .digest();
29
+ this.initializeSessions();
30
+ }
31
+ initializeSessions() {
32
+ try {
33
+ this.reconcileCredentialVerifier();
34
+ this.storage.deleteExpiredSessions(Date.now());
35
+ }
36
+ catch (error) {
37
+ (0, logger_1.debugLog)("Dashboard session initialization failed", {
38
+ errorName: error instanceof Error ? error.name : typeof error,
39
+ });
40
+ throw error;
41
+ }
42
+ }
43
+ deriveCredentialHash(salt) {
44
+ return node_crypto_1.default.scryptSync(JSON.stringify([
45
+ "server-devtools-credentials-v1",
46
+ this.username,
47
+ this.password,
48
+ ]), salt, 32);
49
+ }
50
+ credentialsMatch(salt, hash) {
51
+ const expected = Buffer.from(hash, "hex");
52
+ const actual = this.deriveCredentialHash(Buffer.from(salt, "hex"));
53
+ return expected.length === actual.length &&
54
+ node_crypto_1.default.timingSafeEqual(expected, actual);
55
+ }
56
+ // Sessions are deleted whenever the stored verifier is missing or does not
57
+ // match, because they may have been issued under different credentials.
58
+ reconcileCredentialVerifier() {
59
+ this.verifier = this.storage.reconcileAuthCredentialVerifier(stored => this.credentialsMatch(stored.salt, stored.hash), () => {
60
+ const salt = node_crypto_1.default.randomBytes(16);
61
+ return {
62
+ salt: salt.toString("hex"),
63
+ hash: this.deriveCredentialHash(salt).toString("hex"),
64
+ updatedAt: Date.now(),
65
+ };
66
+ });
17
67
  }
18
68
  generateSessionToken() {
19
69
  return node_crypto_1.default.randomBytes(32).toString("base64url");
20
70
  }
21
71
  hashSessionToken(token) {
22
72
  return node_crypto_1.default
23
- .createHash("sha256")
73
+ .createHmac("sha256", this.sessionKey)
24
74
  .update(token)
25
75
  .digest("hex");
26
76
  }
27
77
  validateCredentials(username, password) {
28
- const normalizedUsername = username ? username?.toLowerCase()?.trim() : null;
78
+ if (typeof username !== "string" || typeof password !== "string") {
79
+ return false;
80
+ }
81
+ const normalizedUsername = username ? username.toLowerCase().trim() : null;
29
82
  return (normalizedUsername === this.username &&
30
83
  password === this.password);
31
84
  }
@@ -36,12 +89,15 @@ class AuthService {
36
89
  const expiresAt = createdAt + (24 * 60 * 60 * 1000);
37
90
  const normalizedUsername = username?.toLowerCase()?.trim();
38
91
  try {
39
- this.storage.saveSession({
92
+ const saved = this.storage.saveSessionForVerifier({
40
93
  sessionHash,
41
94
  username: normalizedUsername,
42
95
  createdAt,
43
96
  expiresAt,
44
- });
97
+ }, this.verifier, createdAt);
98
+ if (!saved) {
99
+ throw new Error("Dashboard credentials changed since this process started");
100
+ }
45
101
  }
46
102
  catch (error) {
47
103
  (0, logger_1.debugLog)("Session creation persistence failed", {
@@ -66,6 +122,8 @@ class AuthService {
66
122
  }
67
123
  if (!session)
68
124
  return undefined;
125
+ if (session.username !== this.username)
126
+ return undefined;
69
127
  const hasExpired = session.expiresAt <= currentDate;
70
128
  if (hasExpired) {
71
129
  try {
@@ -1 +1 @@
1
- {"version":3,"file":"auth.service.js","sourceRoot":"","sources":["../../../src/security/auth.service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"auth.service.js","sourceRoot":"","sources":["../../../src/security/auth.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ declare class RedactionService {
2
+ private readonly redactFields;
3
+ private readonly allowRedacting;
4
+ constructor(allowRedacting: boolean, redactFields: string[]);
5
+ redactData(data: unknown): unknown;
6
+ }
7
+ export default RedactionService;
8
+ //# sourceMappingURL=redaction.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redaction.service.d.ts","sourceRoot":"","sources":["../../../src/security/redaction.service.ts"],"names":[],"mappings":"AAIA,cAAM,gBAAgB;IAClB,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAc;IAC3C,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IAExC,YACI,cAAc,EAAE,OAAO,EACvB,YAAY,EAAE,MAAM,EAAE,EAQzB;IAED,UAAU,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAuBjC;CAIJ;eAEc,gBAAgB"}
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const config_1 = __importDefault(require("../config"));
7
+ class RedactionService {
8
+ redactFields;
9
+ allowRedacting;
10
+ constructor(allowRedacting, redactFields) {
11
+ this.allowRedacting = allowRedacting;
12
+ this.redactFields = new Set(redactFields
13
+ .map(field => field?.trim().toLowerCase())
14
+ .filter(Boolean));
15
+ }
16
+ redactData(data) {
17
+ if (!this.allowRedacting)
18
+ return data;
19
+ if (Array.isArray(data)) {
20
+ return data.map(item => this.redactData(item));
21
+ }
22
+ if (data !== null && typeof data === "object") {
23
+ return Object.fromEntries(Object.entries(data).map(([key, value]) => {
24
+ if (this.redactFields.has(key.toLowerCase()) &&
25
+ value !== undefined) {
26
+ return [key, config_1.default.REDACTED_KEY];
27
+ }
28
+ return [key, this.redactData(value)];
29
+ }));
30
+ }
31
+ return data;
32
+ }
33
+ }
34
+ exports.default = RedactionService;
35
+ //# sourceMappingURL=redaction.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redaction.service.js","sourceRoot":"","sources":["../../../src/security/redaction.service.ts"],"names":[],"mappings":";;;;;AAAA,uDAA+B;AAI/B,MAAM,gBAAgB;IACD,YAAY,CAAc;IAC1B,cAAc,CAAS;IAExC,YACI,cAAuB,EACvB,YAAsB;QAEtB,IAAI,CAAC,cAAc,GAAG,cAAc,CAAA;QACpC,IAAI,CAAC,YAAY,GAAG,IAAI,GAAG,CACvB,YAAY;aACP,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;aACzC,MAAM,CAAC,OAAO,CAAC,CACvB,CAAC;IACN,CAAC;IAED,UAAU,CAAC,IAAa;QACpB,IAAI,CAAC,IAAI,CAAC,cAAc;YAAE,OAAO,IAAI,CAAC;QAEtC,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;YACtB,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC;QACnD,CAAC;QAED,IAAI,IAAI,KAAK,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC5C,OAAO,MAAM,CAAC,WAAW,CACrB,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE;gBACtC,IACI,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;oBACxC,KAAK,KAAK,SAAS,EACrB,CAAC;oBACC,OAAO,CAAC,GAAG,EAAE,gBAAM,CAAC,YAAY,CAAC,CAAC;gBACtC,CAAC;gBAED,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC;YACzC,CAAC,CAAC,CACL,CAAC;QACN,CAAC;QAED,OAAO,IAAI,CAAC;IAChB,CAAC;CAIJ;kBAEc,gBAAgB"}
@@ -1,5 +1,7 @@
1
- import { IDevToolsCurrentUser, IDevToolsSpan, IDevToolsTrace, IHttpClientDetails, ISession, ITraceMetadata, ITraceSummary } from "../types";
1
+ import { DashboardOverviewRange, IAuthCredentialVerifier, IDevToolsCurrentUser, IDevToolsSpan, IDevToolsTrace, IHttpClientDetails, IOverviewMetrics, ISession, ITraceMetadata, ITraceSummary } from "../types";
2
2
  import EncryptDecryptService from "../security/encrypt-decrypt.service";
3
+ export type TraceSummarySortField = "startedAt" | "durationMs" | "statusCode" | "method" | "path";
4
+ export type TraceSummarySortDirection = "asc" | "desc";
3
5
  export default class SQLiteStorage {
4
6
  private readonly encryptionService?;
5
7
  private readonly db;
@@ -7,7 +9,9 @@ export default class SQLiteStorage {
7
9
  private getSchemaVersion;
8
10
  private setSchemaVersion;
9
11
  private initializeV1Schema;
12
+ private initializeV2Schema;
10
13
  private initialize;
14
+ private runImmediateTransaction;
11
15
  private deserializeSensitiveValue;
12
16
  private serializeSensitiveValue;
13
17
  saveSpan(span: IDevToolsSpan): void;
@@ -15,10 +19,11 @@ export default class SQLiteStorage {
15
19
  getTrace(traceId: string): IDevToolsTrace | undefined;
16
20
  saveTraceSummary(span: IDevToolsSpan): void;
17
21
  getTraceSummaries(): ITraceSummary[];
18
- getPaginatedTraceSummaries(page: number, limit: number, search?: string, method?: string, statusCode?: number): {
22
+ getPaginatedTraceSummaries(page: number, limit: number, search?: string, method?: string, statusCode?: number, startedAtFrom?: number, startedAtTo?: number, sortField?: TraceSummarySortField, sortDirection?: TraceSummarySortDirection): {
19
23
  summaries: ITraceSummary[];
20
24
  total: number;
21
25
  };
26
+ getOverview(range: DashboardOverviewRange, now?: number): IOverviewMetrics;
22
27
  saveRequestBody(traceId: string, body: unknown): void;
23
28
  saveResponseBody(traceId: string, body: unknown): void;
24
29
  getTraceMetadata(traceId: string): ITraceMetadata | undefined;
@@ -27,6 +32,10 @@ export default class SQLiteStorage {
27
32
  saveSession(session: ISession): void;
28
33
  getSessionByHash(sessionHash: string): ISession | undefined;
29
34
  deleteSession(sessionHash: string): void;
35
+ deleteExpiredSessions(now: number): number;
36
+ getAuthCredentialVerifier(): IAuthCredentialVerifier | undefined;
37
+ reconcileAuthCredentialVerifier(matches: (stored: IAuthCredentialVerifier) => boolean, createVerifier: () => IAuthCredentialVerifier): IAuthCredentialVerifier;
38
+ saveSessionForVerifier(session: ISession, verifier: IAuthCredentialVerifier, now: number): boolean;
30
39
  saveCurrentUser(traceId: string, user: IDevToolsCurrentUser): void;
31
40
  updateHttpClientDetails(spanId: string, details: Partial<Omit<IHttpClientDetails, "spanId">>): void;
32
41
  getHttpClientDetails(spanId: string): IHttpClientDetails | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"sqlite-storage.d.ts","sourceRoot":"","sources":["../../../src/storage/sqlite-storage.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,aAAa,EAAE,cAAc,EAAE,kBAAkB,EAAE,QAAQ,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAC5I,OAAO,qBAAqB,MAAM,qCAAqC,CAAC;AAUxE,MAAM,CAAC,OAAO,OAAO,aAAa;IAI1B,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAC;IAHvC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAe;IAElC,YAAY,IAAI,EAAE,MAAM,EACH,iBAAiB,CAAC,EAAE,qBAAqB,YAAA,EAK7D;IAED,OAAO,CAAC,gBAAgB;IAQxB,OAAO,CAAC,gBAAgB;IAIxB,OAAO,CAAC,kBAAkB;IA0E1B,OAAO,CAAC,UAAU;IAsBlB,OAAO,CAAC,yBAAyB;IAQjC,OAAO,CAAC,uBAAuB;IAQ/B,QAAQ,CAAC,IAAI,EAAE,aAAa,GAAG,IAAI,CAyClC;IAED,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,aAAa,EAAE,CAkClD;IAED,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAoBpD;IAED,gBAAgB,CAAC,IAAI,EAAE,aAAa,GAAG,IAAI,CA+E1C;IAED,iBAAiB,IAAI,aAAa,EAAE,CAmCnC;IAED,0BAA0B,CACtB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,MAAM,SAAK,EACX,MAAM,CAAC,EAAE,MAAM,EACf,UAAU,CAAC,EAAE,MAAM,GACpB;QACC,SAAS,EAAE,aAAa,EAAE,CAAC;QAC3B,KAAK,EAAE,MAAM,CAAC;KACjB,CAgFA;IAED,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,IAAI,CAmBpD;IAED,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,IAAI,CAmBrD;IAED,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAsD5D;IAED,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAuBjC;IAED,YAAY,IAAI,IAAI,CAenB;IAED,WAAW,CAAC,OAAO,EAAE,QAAQ,GAAG,IAAI,CAenC;IAED,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAmB1D;IAED,aAAa,CAAC,WAAW,EAAE,MAAM,QAKhC;IAED,eAAe,CACX,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,oBAAoB,QAiB7B;IAED,uBAAuB,CACnB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,kBAAkB,EAAE,QAAQ,CAAC,CAAC,GACrD,IAAI,CAsEN;IAED,oBAAoB,CAChB,MAAM,EAAE,MAAM,GACf,kBAAkB,GAAG,SAAS,CA6ChC;IAED,KAAK,IAAI,IAAI,CAEZ;CACJ"}
1
+ {"version":3,"file":"sqlite-storage.d.ts","sourceRoot":"","sources":["../../../src/storage/sqlite-storage.ts"],"names":[],"mappings":"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"}
@@ -91,23 +91,64 @@ class SQLiteStorage {
91
91
  this.db.exec(`ALTER TABLE trace_metadata ADD COLUMN user_json TEXT`);
92
92
  }
93
93
  }
94
+ initializeV2Schema() {
95
+ this.db.exec(`
96
+ CREATE TABLE IF NOT EXISTS auth_credentials (
97
+ id INTEGER PRIMARY KEY CHECK (id = 1),
98
+ salt TEXT NOT NULL,
99
+ hash TEXT NOT NULL,
100
+ updated_at INTEGER NOT NULL
101
+ );
102
+ `);
103
+ }
94
104
  initialize() {
95
- const version = this.getSchemaVersion();
96
- if (version === config_1.default.STORAGE.SCHEMA_VERSION) {
105
+ if (this.getSchemaVersion() >= config_1.default.STORAGE.SCHEMA_VERSION) {
97
106
  return;
98
107
  }
99
- if (version === 0) {
100
- this.db.exec("BEGIN");
101
- try {
108
+ this.runImmediateTransaction(() => {
109
+ // Another process may have migrated while this one waited for the lock.
110
+ const version = this.getSchemaVersion();
111
+ if (version >= config_1.default.STORAGE.SCHEMA_VERSION) {
112
+ return;
113
+ }
114
+ if (version < 1) {
102
115
  this.initializeV1Schema();
103
- this.setSchemaVersion(1);
116
+ }
117
+ if (version < 2) {
118
+ this.initializeV2Schema();
119
+ }
120
+ this.setSchemaVersion(config_1.default.STORAGE.SCHEMA_VERSION);
121
+ });
122
+ }
123
+ // Takes the write lock up front and waits for other processes holding it,
124
+ // so read-check-write sequences cannot interleave across connections.
125
+ runImmediateTransaction(operation) {
126
+ const { timeout: previousTimeout } = this.db
127
+ .prepare("PRAGMA busy_timeout")
128
+ .get();
129
+ this.db.exec(`PRAGMA busy_timeout = ${Math.max(0, Math.floor(config_1.default.STORAGE.BUSY_TIMEOUT_MS))}`);
130
+ try {
131
+ this.db.exec("BEGIN IMMEDIATE");
132
+ try {
133
+ const result = operation();
104
134
  this.db.exec("COMMIT");
135
+ return result;
105
136
  }
106
137
  catch (error) {
107
- this.db.exec("ROLLBACK");
138
+ if (this.db.isTransaction) {
139
+ try {
140
+ this.db.exec("ROLLBACK");
141
+ }
142
+ catch {
143
+ // Preserve the original failure.
144
+ }
145
+ }
108
146
  throw error;
109
147
  }
110
148
  }
149
+ finally {
150
+ this.db.exec(`PRAGMA busy_timeout = ${previousTimeout}`);
151
+ }
111
152
  }
112
153
  deserializeSensitiveValue(value) {
113
154
  const parsed = JSON.parse(value);
@@ -142,7 +183,7 @@ class SQLiteStorage {
142
183
  : null);
143
184
  // Handles the case where the trace summary already exists
144
185
  // when an errored child span arrives.
145
- if (span.error) {
186
+ if (span.status.code === 2 || span.error !== undefined) {
146
187
  this.db.prepare(`
147
188
  UPDATE traces
148
189
  SET has_error = 1
@@ -218,7 +259,10 @@ class SQLiteStorage {
218
259
  SELECT 1
219
260
  FROM spans
220
261
  WHERE trace_id = ?
221
- AND error_json IS NOT NULL
262
+ AND (
263
+ json_extract(status_json, '$.code') = 2
264
+ OR error_json IS NOT NULL
265
+ )
222
266
  LIMIT 1
223
267
  `).get(span.traceId);
224
268
  const hasError = errorSpan ? 1 : 0;
@@ -287,14 +331,15 @@ class SQLiteStorage {
287
331
  };
288
332
  });
289
333
  }
290
- getPaginatedTraceSummaries(page, limit, search = "", method, statusCode) {
334
+ getPaginatedTraceSummaries(page, limit, search = "", method, statusCode, startedAtFrom, startedAtTo, sortField = "startedAt", sortDirection = "desc") {
291
335
  const offset = (page - 1) * limit;
292
336
  const conditions = [];
293
337
  const parameters = [];
294
338
  const normalizedSearch = search.trim();
295
339
  if (normalizedSearch) {
296
340
  conditions.push(`(
297
- t.path LIKE ? COLLATE NOCASE ESCAPE '\\'
341
+ t.trace_id LIKE ? COLLATE NOCASE ESCAPE '\\'
342
+ OR t.path LIKE ? COLLATE NOCASE ESCAPE '\\'
298
343
  OR t.route LIKE ? COLLATE NOCASE ESCAPE '\\'
299
344
  OR EXISTS (
300
345
  SELECT 1
@@ -303,10 +348,18 @@ class SQLiteStorage {
303
348
  AND s.span_id = t.root_span_id
304
349
  AND s.name LIKE ? COLLATE NOCASE ESCAPE '\\'
305
350
  )
351
+ OR EXISTS (
352
+ SELECT 1
353
+ FROM spans s
354
+ WHERE s.trace_id = t.trace_id
355
+ AND s.span_id = t.root_span_id
356
+ AND json_extract(s.attributes_json, '$."url.query"')
357
+ LIKE ? COLLATE NOCASE ESCAPE '\\'
358
+ )
306
359
  )`);
307
360
  const escapedSearch = normalizedSearch.replace(/[\\%_]/g, character => `\\${character}`);
308
361
  const searchPattern = `%${escapedSearch}%`;
309
- parameters.push(searchPattern, searchPattern, searchPattern);
362
+ parameters.push(searchPattern, searchPattern, searchPattern, searchPattern, searchPattern);
310
363
  }
311
364
  const normalizedMethod = method?.trim();
312
365
  if (normalizedMethod) {
@@ -317,9 +370,27 @@ class SQLiteStorage {
317
370
  conditions.push("t.status_code = ?");
318
371
  parameters.push(statusCode);
319
372
  }
373
+ if (startedAtFrom !== undefined) {
374
+ conditions.push("t.started_at >= ?");
375
+ parameters.push(startedAtFrom);
376
+ }
377
+ if (startedAtTo !== undefined) {
378
+ conditions.push("t.started_at < ?");
379
+ parameters.push(startedAtTo);
380
+ }
320
381
  const whereClause = conditions.length > 0
321
382
  ? `WHERE ${conditions.join(" AND ")}`
322
383
  : "";
384
+ const sortColumns = {
385
+ startedAt: "t.started_at",
386
+ durationMs: "t.duration_ms",
387
+ statusCode: "t.status_code",
388
+ method: "t.method",
389
+ path: "t.path",
390
+ };
391
+ const sortColumn = sortColumns[sortField];
392
+ const sortNullsLast = `CASE WHEN ${sortColumn} IS NULL THEN 1 ELSE 0 END ASC`;
393
+ const sortOrder = `${sortColumn} ${sortDirection.toUpperCase()}`;
323
394
  const totalRow = this.db.prepare(`
324
395
  SELECT COUNT(*) AS total
325
396
  FROM traces t
@@ -335,7 +406,7 @@ class SQLiteStorage {
335
406
  LEFT JOIN trace_metadata tm
336
407
  ON tm.trace_id = t.trace_id
337
408
  ${whereClause}
338
- ORDER BY t.started_at DESC
409
+ ORDER BY ${sortNullsLast}, ${sortOrder}, t.trace_id ASC
339
410
  LIMIT ? OFFSET ?
340
411
  `).all(...parameters, limit, offset);
341
412
  const summaries = rows.map((row) => {
@@ -361,6 +432,64 @@ class SQLiteStorage {
361
432
  });
362
433
  return { summaries, total: totalRow.total };
363
434
  }
435
+ getOverview(range, now = Date.now()) {
436
+ const rangeMs = {
437
+ "15m": 15 * 60 * 1000,
438
+ "1h": 60 * 60 * 1000,
439
+ "6h": 6 * 60 * 60 * 1000,
440
+ "24h": 24 * 60 * 60 * 1000,
441
+ };
442
+ const to = now;
443
+ const from = to - rangeMs[range];
444
+ const bucketCount = range === "15m" ? 15 : range === "1h" ? 12 : 24;
445
+ const bucketWidth = rangeMs[range] / bucketCount;
446
+ const rows = this.db.prepare(`
447
+ SELECT trace_id, started_at, duration_ms, method, path, route, status_code, has_error
448
+ FROM traces
449
+ WHERE started_at >= ? AND started_at < ?
450
+ ORDER BY started_at DESC
451
+ `).all(from, to);
452
+ const durations = rows
453
+ .map(row => row.duration_ms)
454
+ .filter((duration) => duration !== null && Number.isFinite(duration));
455
+ const failures = rows
456
+ .filter(row => row.status_code !== null && row.status_code >= 500 && row.status_code < 600)
457
+ .slice(0, 10)
458
+ .map(row => ({
459
+ id: row.trace_id,
460
+ method: row.method ?? "—",
461
+ path: row.path ?? "—",
462
+ route: row.route ?? undefined,
463
+ statusCode: row.status_code ?? undefined,
464
+ durationMs: row.duration_ms ?? undefined,
465
+ startedAt: row.started_at,
466
+ hasError: row.has_error === 1,
467
+ }));
468
+ const volume = Array.from({ length: bucketCount }, (_, index) => {
469
+ const startAt = from + index * bucketWidth;
470
+ const endAt = index === bucketCount - 1 ? to : startAt + bucketWidth;
471
+ return {
472
+ startAt,
473
+ endAt,
474
+ count: rows.filter(row => row.started_at >= startAt && row.started_at < endAt).length,
475
+ };
476
+ });
477
+ return {
478
+ range,
479
+ from,
480
+ to,
481
+ totalRequests: rows.length,
482
+ errorRate: rows.length === 0 ? 0 : failures.length > 0
483
+ ? rows.filter(row => row.status_code !== null && row.status_code >= 500 && row.status_code < 600).length / rows.length
484
+ : 0,
485
+ averageLatencyMs: durations.length === 0
486
+ ? 0
487
+ : durations.reduce((sum, duration) => sum + duration, 0) / durations.length,
488
+ slowRequests: durations.filter(duration => duration > 500).length,
489
+ volume,
490
+ recentFailures: failures,
491
+ };
492
+ }
364
493
  saveRequestBody(traceId, body) {
365
494
  const statement = this.db.prepare(`
366
495
  INSERT INTO trace_metadata (
@@ -507,6 +636,58 @@ class SQLiteStorage {
507
636
  WHERE session_hash = ?
508
637
  `).run(sessionHash);
509
638
  }
639
+ deleteExpiredSessions(now) {
640
+ const result = this.db.prepare(`
641
+ DELETE FROM sessions
642
+ WHERE expires_at <= ?
643
+ `).run(now);
644
+ return Number(result.changes);
645
+ }
646
+ getAuthCredentialVerifier() {
647
+ const row = this.db.prepare(`
648
+ SELECT salt, hash, updated_at
649
+ FROM auth_credentials
650
+ WHERE id = 1
651
+ `).get();
652
+ if (!row)
653
+ return undefined;
654
+ return {
655
+ salt: row.salt,
656
+ hash: row.hash,
657
+ updatedAt: row.updated_at,
658
+ };
659
+ }
660
+ reconcileAuthCredentialVerifier(matches, createVerifier) {
661
+ return this.runImmediateTransaction(() => {
662
+ const stored = this.getAuthCredentialVerifier();
663
+ if (stored && matches(stored)) {
664
+ return stored;
665
+ }
666
+ const verifier = createVerifier();
667
+ this.db.exec("DELETE FROM sessions");
668
+ this.db.prepare(`
669
+ INSERT OR REPLACE INTO auth_credentials (
670
+ id,
671
+ salt,
672
+ hash,
673
+ updated_at
674
+ )
675
+ VALUES (1, ?, ?, ?)
676
+ `).run(verifier.salt, verifier.hash, verifier.updatedAt);
677
+ return verifier;
678
+ });
679
+ }
680
+ saveSessionForVerifier(session, verifier, now) {
681
+ return this.runImmediateTransaction(() => {
682
+ const stored = this.getAuthCredentialVerifier();
683
+ if (!stored || stored.salt !== verifier.salt || stored.hash !== verifier.hash) {
684
+ return false;
685
+ }
686
+ this.deleteExpiredSessions(now);
687
+ this.saveSession(session);
688
+ return true;
689
+ });
690
+ }
510
691
  saveCurrentUser(traceId, user) {
511
692
  const processedUser = this.encryptionService
512
693
  ? this.encryptionService.encryptData(user)