Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2ad38dece3 | |||
| 32cb5bb423 | |||
| 5fa97322fb | |||
| af16473495 | |||
| 748a60ef74 | |||
| 3f71643e81 | |||
| 9f107b6876 | |||
| 4a8cd4b4b7 |
@@ -1,5 +1,12 @@
|
||||
# Changelog
|
||||
|
||||
## 2025-12-04 - 7.8.11 - fix(web_inject)
|
||||
Improve logging in web injection (TypedRequest) and update dees-comms dependency
|
||||
|
||||
- Add debug logging to ts_web_inject to explicitly filter serviceworker_* methods and avoid infinite loops
|
||||
- Log incoming TypedRequest methods for better visibility during debugging
|
||||
- Bump dependency @design.estate/dees-comms from ^1.0.27 to ^1.0.28
|
||||
|
||||
## 2025-12-04 - 7.8.0 - feat(serviceworker)
|
||||
Add TypedRequest traffic monitoring and SW dashboard 'Requests' panel
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@api.global/typedserver",
|
||||
"version": "7.8.9",
|
||||
"version": "7.8.14",
|
||||
"description": "A TypeScript-based project for easy serving of static files with support for live reloading, compression, and typed requests.",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
@@ -58,11 +58,11 @@
|
||||
],
|
||||
"homepage": "https://code.foss.global/api.global/typedserver",
|
||||
"dependencies": {
|
||||
"@api.global/typedrequest": "^3.2.1",
|
||||
"@api.global/typedrequest": "^3.2.5",
|
||||
"@api.global/typedrequest-interfaces": "^3.0.19",
|
||||
"@api.global/typedsocket": "^4.1.0",
|
||||
"@cloudflare/workers-types": "^4.20251202.0",
|
||||
"@design.estate/dees-comms": "^1.0.27",
|
||||
"@design.estate/dees-comms": "^1.0.30",
|
||||
"@push.rocks/lik": "^6.2.2",
|
||||
"@push.rocks/smartdelay": "^3.0.5",
|
||||
"@push.rocks/smartenv": "^6.0.0",
|
||||
|
||||
73
pnpm-lock.yaml
generated
73
pnpm-lock.yaml
generated
@@ -9,8 +9,8 @@ importers:
|
||||
.:
|
||||
dependencies:
|
||||
'@api.global/typedrequest':
|
||||
specifier: ^3.2.1
|
||||
version: 3.2.1
|
||||
specifier: ^3.2.5
|
||||
version: 3.2.5
|
||||
'@api.global/typedrequest-interfaces':
|
||||
specifier: ^3.0.19
|
||||
version: 3.0.19
|
||||
@@ -21,8 +21,8 @@ importers:
|
||||
specifier: ^4.20251202.0
|
||||
version: 4.20251202.0
|
||||
'@design.estate/dees-comms':
|
||||
specifier: ^1.0.27
|
||||
version: 1.0.27
|
||||
specifier: ^1.0.30
|
||||
version: 1.0.30
|
||||
'@push.rocks/lik':
|
||||
specifier: ^6.2.2
|
||||
version: 6.2.2
|
||||
@@ -135,8 +135,8 @@ packages:
|
||||
'@api.global/typedrequest-interfaces@3.0.19':
|
||||
resolution: {integrity: sha512-uuHUXJeOy/inWSDrwD0Cwax2rovpxYllDhM2RWh+6mVpQuNmZ3uw6IVg6dA2G1rOe24Ebs+Y9SzEogo+jYN7vw==}
|
||||
|
||||
'@api.global/typedrequest@3.2.1':
|
||||
resolution: {integrity: sha512-BDgKC+F5H4OriFG5kbfSY5MdF5b9hGvBnYt25sETOXyU7ZPj4vF9OhhPthMls3SORqQEc3FxoNuCLn7hIVEN9g==}
|
||||
'@api.global/typedrequest@3.2.5':
|
||||
resolution: {integrity: sha512-LM/sUTuYnU5xY4gNZrN6ERMiKr+SpDZuSxJkAZz1YazC7ymGfo6uQ8sCnN8eNNQNFqIOkC+BtfYRayfbGwYLLg==}
|
||||
|
||||
'@api.global/typedserver@3.0.80':
|
||||
resolution: {integrity: sha512-dcp0oXsjBL+XdFg1wUUP08uJQid5bQ0Yv3V3Y3lnI2QCbat0FU+Tsb0TZRnZ4+P150Vj/ITBqJUgDzFsF34grA==}
|
||||
@@ -533,8 +533,8 @@ packages:
|
||||
resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/runtime@7.23.4':
|
||||
resolution: {integrity: sha512-2Yv65nlWnWlSpe3fXEyX5i7fx5kIKo4Qbcj+hMO0odwaneFjfXw5fdum+4yL20O0QiaHpia0cYQ9xpNMqrBwHg==}
|
||||
'@babel/runtime@7.28.4':
|
||||
resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@borewit/text-codec@0.1.1':
|
||||
@@ -546,8 +546,8 @@ packages:
|
||||
'@configvault.io/interfaces@1.0.17':
|
||||
resolution: {integrity: sha512-bEcCUR2VBDJsTin8HQh8Uw/mlYl2v8A3jMIaQ+MTB9Hrqd6CZL2dL7iJdWyFl/3EIX+LDxWFR+Oq7liIq7w+1Q==}
|
||||
|
||||
'@design.estate/dees-comms@1.0.27':
|
||||
resolution: {integrity: sha512-GvzTUwkV442LD60T08iqSoqvhA02Mou5lFvvqBPc4yBUiU7cZISqBx+76xvMgMIEI9Dx9JfTl4/2nW8MoVAanw==}
|
||||
'@design.estate/dees-comms@1.0.30':
|
||||
resolution: {integrity: sha512-KchMlklJfKAjQiJiR0xmofXtQ27VgZtBIxcMwPE9d+h3jJRv+lPZxzBQVOM0eyM0uS44S5vJMZ11IeV4uDXSHg==}
|
||||
|
||||
'@design.estate/dees-domtools@2.0.65':
|
||||
resolution: {integrity: sha512-BA+xfCqiCr3fDt2BLaUgW979083Vfm01W6QJ8IclcbINggSDBmAEhfU+CVdxeogwa/d9/ctxY12suG77dqBjaA==}
|
||||
@@ -2018,8 +2018,8 @@ packages:
|
||||
brace-expansion@2.0.2:
|
||||
resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==}
|
||||
|
||||
broadcast-channel@7.0.0:
|
||||
resolution: {integrity: sha512-a2tW0Ia1pajcPBOGUF2jXlDnvE9d5/dg6BG9h60OmRUcZVr/veUrU8vEQFwwQIhwG3KVzYwSk3v2nRRGFgQDXQ==}
|
||||
broadcast-channel@7.2.0:
|
||||
resolution: {integrity: sha512-JgraikEriG/TxBUi2W/w2O0jhHjXZUtXAvCZH0Yr3whjxYVgAg0hSe6r/teM+I5H5Q/q6RhyuKdC2pHNlFyepQ==}
|
||||
|
||||
brorand@1.1.0:
|
||||
resolution: {integrity: sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=}
|
||||
@@ -3161,8 +3161,8 @@ packages:
|
||||
resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
oblivious-set@1.4.0:
|
||||
resolution: {integrity: sha512-szyd0ou0T8nsAqHtprRcP3WidfsN1TnAR5yWXf2mFCEr5ek3LEOkT6EZ/92Xfs74HIdyhG5WkGxIssMU0jBaeg==}
|
||||
oblivious-set@2.0.0:
|
||||
resolution: {integrity: sha512-QOUH5Xrsced9fKXaQTjWoDGKeS/Or7E2jB0FN63N4mkAO4qJdB7WR7e6qWAOHM5nk25FJ8TGjhP7DH4l6vFVLg==}
|
||||
engines: {node: '>=16'}
|
||||
|
||||
observable-fns@0.6.1:
|
||||
@@ -3384,9 +3384,6 @@ packages:
|
||||
reflect-metadata@0.2.2:
|
||||
resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==}
|
||||
|
||||
regenerator-runtime@0.14.1:
|
||||
resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==}
|
||||
|
||||
registry-auth-token@5.1.0:
|
||||
resolution: {integrity: sha512-GdekYuwLXLxMuFTwAPg5UKGLW/UXzQrZvH/Zj791BQif5T05T0RsaLfHc9q3ZOKi7n+BoprPD9mJ0O0k4xzUlw==}
|
||||
engines: {node: '>=14'}
|
||||
@@ -3896,7 +3893,7 @@ snapshots:
|
||||
|
||||
'@api.global/typedrequest-interfaces@3.0.19': {}
|
||||
|
||||
'@api.global/typedrequest@3.2.1':
|
||||
'@api.global/typedrequest@3.2.5':
|
||||
dependencies:
|
||||
'@api.global/typedrequest-interfaces': 3.0.19
|
||||
'@push.rocks/isounique': 1.0.5
|
||||
@@ -3910,11 +3907,11 @@ snapshots:
|
||||
|
||||
'@api.global/typedserver@3.0.80(@push.rocks/smartserve@1.1.2)':
|
||||
dependencies:
|
||||
'@api.global/typedrequest': 3.2.1
|
||||
'@api.global/typedrequest': 3.2.5
|
||||
'@api.global/typedrequest-interfaces': 3.0.19
|
||||
'@api.global/typedsocket': 3.1.1(@push.rocks/smartserve@1.1.2)
|
||||
'@cloudflare/workers-types': 4.20251202.0
|
||||
'@design.estate/dees-comms': 1.0.27
|
||||
'@design.estate/dees-comms': 1.0.30
|
||||
'@push.rocks/lik': 6.2.2
|
||||
'@push.rocks/smartchok': 1.1.1
|
||||
'@push.rocks/smartdelay': 3.0.5
|
||||
@@ -3958,7 +3955,7 @@ snapshots:
|
||||
|
||||
'@api.global/typedsocket@3.1.1(@push.rocks/smartserve@1.1.2)':
|
||||
dependencies:
|
||||
'@api.global/typedrequest': 3.2.1
|
||||
'@api.global/typedrequest': 3.2.5
|
||||
'@api.global/typedrequest-interfaces': 3.0.19
|
||||
'@push.rocks/isohash': 2.0.1
|
||||
'@push.rocks/smartjson': 5.2.0
|
||||
@@ -3978,7 +3975,7 @@ snapshots:
|
||||
|
||||
'@api.global/typedsocket@4.1.0(@push.rocks/smartserve@1.1.2)':
|
||||
dependencies:
|
||||
'@api.global/typedrequest': 3.2.1
|
||||
'@api.global/typedrequest': 3.2.5
|
||||
'@api.global/typedrequest-interfaces': 3.0.19
|
||||
'@push.rocks/isohash': 2.0.1
|
||||
'@push.rocks/smartdelay': 3.0.5
|
||||
@@ -5245,9 +5242,7 @@ snapshots:
|
||||
|
||||
'@babel/helper-validator-identifier@7.28.5': {}
|
||||
|
||||
'@babel/runtime@7.23.4':
|
||||
dependencies:
|
||||
regenerator-runtime: 0.14.1
|
||||
'@babel/runtime@7.28.4': {}
|
||||
|
||||
'@borewit/text-codec@0.1.1': {}
|
||||
|
||||
@@ -5257,17 +5252,17 @@ snapshots:
|
||||
dependencies:
|
||||
'@api.global/typedrequest-interfaces': 3.0.19
|
||||
|
||||
'@design.estate/dees-comms@1.0.27':
|
||||
'@design.estate/dees-comms@1.0.30':
|
||||
dependencies:
|
||||
'@api.global/typedrequest': 3.2.1
|
||||
'@api.global/typedrequest': 3.2.5
|
||||
'@api.global/typedrequest-interfaces': 3.0.19
|
||||
'@push.rocks/smartdelay': 3.0.5
|
||||
broadcast-channel: 7.0.0
|
||||
broadcast-channel: 7.2.0
|
||||
|
||||
'@design.estate/dees-domtools@2.0.65':
|
||||
dependencies:
|
||||
'@api.global/typedrequest': 3.2.1
|
||||
'@design.estate/dees-comms': 1.0.27
|
||||
'@api.global/typedrequest': 3.2.5
|
||||
'@design.estate/dees-comms': 1.0.30
|
||||
'@push.rocks/lik': 6.2.2
|
||||
'@push.rocks/smartdelay': 3.0.5
|
||||
'@push.rocks/smartjson': 5.2.0
|
||||
@@ -5288,8 +5283,8 @@ snapshots:
|
||||
|
||||
'@design.estate/dees-domtools@2.3.6':
|
||||
dependencies:
|
||||
'@api.global/typedrequest': 3.2.1
|
||||
'@design.estate/dees-comms': 1.0.27
|
||||
'@api.global/typedrequest': 3.2.5
|
||||
'@design.estate/dees-comms': 1.0.30
|
||||
'@push.rocks/lik': 6.2.2
|
||||
'@push.rocks/smartdelay': 3.0.5
|
||||
'@push.rocks/smartjson': 5.2.0
|
||||
@@ -5889,7 +5884,7 @@ snapshots:
|
||||
|
||||
'@push.rocks/qenv@6.1.3':
|
||||
dependencies:
|
||||
'@api.global/typedrequest': 3.2.1
|
||||
'@api.global/typedrequest': 3.2.5
|
||||
'@configvault.io/interfaces': 1.0.17
|
||||
'@push.rocks/smartfile': 11.2.7
|
||||
'@push.rocks/smartlog': 3.1.10
|
||||
@@ -6376,7 +6371,7 @@ snapshots:
|
||||
|
||||
'@push.rocks/smartserve@1.1.2':
|
||||
dependencies:
|
||||
'@api.global/typedrequest': 3.2.1
|
||||
'@api.global/typedrequest': 3.2.5
|
||||
'@push.rocks/lik': 6.2.2
|
||||
'@push.rocks/smartenv': 6.0.0
|
||||
'@push.rocks/smartlog': 3.1.10
|
||||
@@ -7463,10 +7458,10 @@ snapshots:
|
||||
dependencies:
|
||||
balanced-match: 1.0.2
|
||||
|
||||
broadcast-channel@7.0.0:
|
||||
broadcast-channel@7.2.0:
|
||||
dependencies:
|
||||
'@babel/runtime': 7.23.4
|
||||
oblivious-set: 1.4.0
|
||||
'@babel/runtime': 7.28.4
|
||||
oblivious-set: 2.0.0
|
||||
p-queue: 6.6.2
|
||||
unload: 2.4.1
|
||||
|
||||
@@ -8886,7 +8881,7 @@ snapshots:
|
||||
|
||||
object-keys@1.1.1: {}
|
||||
|
||||
oblivious-set@1.4.0: {}
|
||||
oblivious-set@2.0.0: {}
|
||||
|
||||
observable-fns@0.6.1: {}
|
||||
|
||||
@@ -9130,8 +9125,6 @@ snapshots:
|
||||
|
||||
reflect-metadata@0.2.2: {}
|
||||
|
||||
regenerator-runtime@0.14.1: {}
|
||||
|
||||
registry-auth-token@5.1.0:
|
||||
dependencies:
|
||||
'@pnpm/npm-conf': 2.3.1
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@api.global/typedserver',
|
||||
version: '7.8.0',
|
||||
version: '7.8.11',
|
||||
description: 'A TypeScript-based project for easy serving of static files with support for live reloading, compression, and typed requests.'
|
||||
}
|
||||
|
||||
@@ -21,6 +21,19 @@ export interface ITypedRequestStats {
|
||||
avgDurationMs: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Grouped request/response pair by correlationId
|
||||
*/
|
||||
export interface IGroupedRequest {
|
||||
correlationId: string;
|
||||
method: string;
|
||||
request?: ITypedRequestLogEntry;
|
||||
response?: ITypedRequestLogEntry;
|
||||
timestamp: number;
|
||||
durationMs?: number;
|
||||
hasError: boolean;
|
||||
}
|
||||
|
||||
type TRequestFilter = 'all' | 'outgoing' | 'incoming';
|
||||
type TPhaseFilter = 'all' | 'request' | 'response';
|
||||
|
||||
@@ -159,20 +172,6 @@ export class SwDashRequests extends LitElement {
|
||||
color: var(--accent-error);
|
||||
}
|
||||
|
||||
.request-payload {
|
||||
font-size: 11px;
|
||||
color: var(--text-tertiary);
|
||||
background: var(--bg-tertiary);
|
||||
padding: var(--space-2);
|
||||
border-radius: var(--radius-sm);
|
||||
font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
max-height: 150px;
|
||||
overflow-y: auto;
|
||||
margin-top: var(--space-2);
|
||||
}
|
||||
|
||||
.request-error {
|
||||
font-size: 12px;
|
||||
color: var(--accent-error);
|
||||
@@ -288,22 +287,188 @@ export class SwDashRequests extends LitElement {
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
.toggle-payload {
|
||||
font-size: 11px;
|
||||
color: var(--accent-primary);
|
||||
cursor: pointer;
|
||||
margin-top: var(--space-1);
|
||||
}
|
||||
|
||||
.toggle-payload:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.correlation-id {
|
||||
font-size: 10px;
|
||||
color: var(--text-tertiary);
|
||||
font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
|
||||
}
|
||||
|
||||
/* Grouped request card */
|
||||
.request-card .request-response-badges {
|
||||
display: flex;
|
||||
gap: var(--space-2);
|
||||
margin-top: var(--space-1);
|
||||
}
|
||||
|
||||
.request-card .status-badge {
|
||||
font-size: 10px;
|
||||
padding: 2px 6px;
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
.status-badge.has-request {
|
||||
background: rgba(251, 191, 36, 0.15);
|
||||
color: var(--accent-warning);
|
||||
}
|
||||
|
||||
.status-badge.has-response {
|
||||
background: rgba(99, 102, 241, 0.15);
|
||||
color: var(--accent-primary);
|
||||
}
|
||||
|
||||
.status-badge.pending {
|
||||
background: rgba(156, 163, 175, 0.15);
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
.btn-show-payload {
|
||||
background: var(--bg-tertiary);
|
||||
border: 1px solid var(--border-default);
|
||||
color: var(--accent-primary);
|
||||
font-size: 11px;
|
||||
padding: var(--space-1) var(--space-2);
|
||||
border-radius: var(--radius-sm);
|
||||
cursor: pointer;
|
||||
margin-top: var(--space-2);
|
||||
}
|
||||
|
||||
.btn-show-payload:hover {
|
||||
background: var(--accent-primary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Modal styles */
|
||||
.payload-modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
z-index: 10000;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: var(--space-4);
|
||||
}
|
||||
|
||||
.payload-modal {
|
||||
background: var(--bg-primary);
|
||||
border-radius: var(--radius-lg);
|
||||
border: 1px solid var(--border-default);
|
||||
width: 100%;
|
||||
max-width: 1400px;
|
||||
height: 90vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: var(--space-3) var(--space-4);
|
||||
border-bottom: 1px solid var(--border-default);
|
||||
background: var(--bg-secondary);
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
|
||||
}
|
||||
|
||||
.modal-subtitle {
|
||||
font-size: 11px;
|
||||
color: var(--text-tertiary);
|
||||
margin-top: var(--space-1);
|
||||
}
|
||||
|
||||
.modal-close {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--text-tertiary);
|
||||
font-size: 24px;
|
||||
cursor: pointer;
|
||||
padding: var(--space-1);
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.modal-close:hover {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 1px;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
background: var(--border-default);
|
||||
}
|
||||
|
||||
.payload-panel {
|
||||
background: var(--bg-primary);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.payload-panel-header {
|
||||
padding: var(--space-2) var(--space-3);
|
||||
background: var(--bg-secondary);
|
||||
border-bottom: 1px solid var(--border-default);
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
.payload-panel-header .badge {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.payload-panel-content {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
padding: var(--space-3);
|
||||
}
|
||||
|
||||
.payload-json {
|
||||
font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.payload-empty {
|
||||
color: var(--text-tertiary);
|
||||
font-style: italic;
|
||||
font-size: 12px;
|
||||
padding: var(--space-4);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.payload-meta {
|
||||
font-size: 11px;
|
||||
color: var(--text-tertiary);
|
||||
padding: var(--space-2) var(--space-3);
|
||||
border-top: 1px solid var(--border-default);
|
||||
background: var(--bg-tertiary);
|
||||
}
|
||||
|
||||
.payload-error {
|
||||
background: rgba(239, 68, 68, 0.1);
|
||||
color: var(--accent-error);
|
||||
padding: var(--space-2) var(--space-3);
|
||||
font-size: 12px;
|
||||
border-bottom: 1px solid rgba(239, 68, 68, 0.2);
|
||||
}
|
||||
`
|
||||
];
|
||||
|
||||
@@ -318,9 +483,12 @@ export class SwDashRequests extends LitElement {
|
||||
@state() accessor phaseFilter: TPhaseFilter = 'all';
|
||||
@state() accessor methodFilter = '';
|
||||
@state() accessor searchText = '';
|
||||
@state() accessor expandedPayloads: Set<string> = new Set();
|
||||
@state() accessor isLoadingMore = false;
|
||||
|
||||
// Modal state
|
||||
@state() accessor modalOpen = false;
|
||||
@state() accessor selectedGroup: IGroupedRequest | null = null;
|
||||
|
||||
private handleDirectionFilterChange(e: Event): void {
|
||||
this.directionFilter = (e.target as HTMLSelectElement).value as TRequestFilter;
|
||||
// Local filtering - no HTTP request
|
||||
@@ -373,14 +541,36 @@ export class SwDashRequests extends LitElement {
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
private togglePayload(correlationId: string): void {
|
||||
const newSet = new Set(this.expandedPayloads);
|
||||
if (newSet.has(correlationId)) {
|
||||
newSet.delete(correlationId);
|
||||
} else {
|
||||
newSet.add(correlationId);
|
||||
private openPayloadModal(group: IGroupedRequest): void {
|
||||
this.selectedGroup = group;
|
||||
this.modalOpen = true;
|
||||
}
|
||||
|
||||
private closeModal(): void {
|
||||
this.modalOpen = false;
|
||||
this.selectedGroup = null;
|
||||
}
|
||||
|
||||
private handleModalOverlayClick(e: Event): void {
|
||||
if ((e.target as HTMLElement).classList.contains('payload-modal-overlay')) {
|
||||
this.closeModal();
|
||||
}
|
||||
this.expandedPayloads = newSet;
|
||||
}
|
||||
|
||||
private handleKeydown = (e: KeyboardEvent): void => {
|
||||
if (e.key === 'Escape' && this.modalOpen) {
|
||||
this.closeModal();
|
||||
}
|
||||
};
|
||||
|
||||
connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
document.addEventListener('keydown', this.handleKeydown);
|
||||
}
|
||||
|
||||
disconnectedCallback(): void {
|
||||
super.disconnectedCallback();
|
||||
document.removeEventListener('keydown', this.handleKeydown);
|
||||
}
|
||||
|
||||
private formatTimestamp(ts: number): string {
|
||||
@@ -440,10 +630,127 @@ export class SwDashRequests extends LitElement {
|
||||
return result;
|
||||
}
|
||||
|
||||
public render(): TemplateResult {
|
||||
const filteredLogs = this.getFilteredLogs();
|
||||
/**
|
||||
* Group filtered logs by correlationId to show request/response pairs together
|
||||
*/
|
||||
private getGroupedLogs(): IGroupedRequest[] {
|
||||
const filtered = this.getFilteredLogs();
|
||||
const groups = new Map<string, IGroupedRequest>();
|
||||
|
||||
for (const log of filtered) {
|
||||
let group = groups.get(log.correlationId);
|
||||
|
||||
if (!group) {
|
||||
group = {
|
||||
correlationId: log.correlationId,
|
||||
method: log.method,
|
||||
timestamp: log.timestamp,
|
||||
hasError: false,
|
||||
};
|
||||
groups.set(log.correlationId, group);
|
||||
}
|
||||
|
||||
if (log.phase === 'request') {
|
||||
group.request = log;
|
||||
// Update timestamp to the earliest (request time)
|
||||
if (log.timestamp < group.timestamp) {
|
||||
group.timestamp = log.timestamp;
|
||||
}
|
||||
} else if (log.phase === 'response') {
|
||||
group.response = log;
|
||||
if (log.durationMs !== undefined) {
|
||||
group.durationMs = log.durationMs;
|
||||
}
|
||||
}
|
||||
|
||||
if (log.error) {
|
||||
group.hasError = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Convert to array and sort by timestamp (newest first)
|
||||
return Array.from(groups.values()).sort((a, b) => b.timestamp - a.timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the payload modal
|
||||
*/
|
||||
private renderModal(): TemplateResult | null {
|
||||
if (!this.modalOpen || !this.selectedGroup) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const group = this.selectedGroup;
|
||||
|
||||
return html`
|
||||
<div class="payload-modal-overlay" @click="${this.handleModalOverlayClick}">
|
||||
<div class="payload-modal">
|
||||
<div class="modal-header">
|
||||
<div>
|
||||
<div class="modal-title">${group.method}</div>
|
||||
<div class="modal-subtitle">
|
||||
Correlation ID: ${group.correlationId}
|
||||
${group.durationMs !== undefined ? html` | Duration: ${this.formatDuration(group.durationMs)}` : ''}
|
||||
</div>
|
||||
</div>
|
||||
<button class="modal-close" @click="${this.closeModal}">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<!-- Request Panel (Left) -->
|
||||
<div class="payload-panel">
|
||||
<div class="payload-panel-header">
|
||||
<span class="badge phase-request">REQUEST</span>
|
||||
${group.request ? html`
|
||||
<span class="badge direction-${group.request.direction}">${group.request.direction}</span>
|
||||
` : ''}
|
||||
</div>
|
||||
${group.request ? html`
|
||||
<div class="payload-meta">
|
||||
Timestamp: ${this.formatTimestamp(group.request.timestamp)}
|
||||
</div>
|
||||
<div class="payload-panel-content">
|
||||
<pre class="payload-json">${JSON.stringify(group.request.payload, null, 2)}</pre>
|
||||
</div>
|
||||
` : html`
|
||||
<div class="payload-empty">No request data captured</div>
|
||||
`}
|
||||
</div>
|
||||
|
||||
<!-- Response Panel (Right) -->
|
||||
<div class="payload-panel">
|
||||
<div class="payload-panel-header">
|
||||
<span class="badge phase-response">RESPONSE</span>
|
||||
${group.response ? html`
|
||||
<span class="badge direction-${group.response.direction}">${group.response.direction}</span>
|
||||
` : ''}
|
||||
</div>
|
||||
${group.response?.error ? html`
|
||||
<div class="payload-error">Error: ${group.response.error}</div>
|
||||
` : ''}
|
||||
${group.response ? html`
|
||||
<div class="payload-meta">
|
||||
Timestamp: ${this.formatTimestamp(group.response.timestamp)}
|
||||
${group.response.durationMs !== undefined ? html` | Duration: ${this.formatDuration(group.response.durationMs)}` : ''}
|
||||
</div>
|
||||
<div class="payload-panel-content">
|
||||
<pre class="payload-json">${JSON.stringify(group.response.payload, null, 2)}</pre>
|
||||
</div>
|
||||
` : html`
|
||||
<div class="payload-empty">No response yet (pending)</div>
|
||||
`}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
public render(): TemplateResult {
|
||||
const groupedLogs = this.getGroupedLogs();
|
||||
|
||||
return html`
|
||||
${this.renderModal()}
|
||||
|
||||
<!-- Stats Bar -->
|
||||
<div class="stats-bar">
|
||||
<div class="stat-item">
|
||||
@@ -463,7 +770,7 @@ export class SwDashRequests extends LitElement {
|
||||
<span class="stat-label">Avg Duration</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="stat-value">${filteredLogs.length}</span>
|
||||
<span class="stat-value">${groupedLogs.length}</span>
|
||||
<span class="stat-label">Showing</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -523,46 +830,51 @@ export class SwDashRequests extends LitElement {
|
||||
<button class="btn clear-btn" @click="${this.handleClear}">Clear Logs</button>
|
||||
</div>
|
||||
|
||||
<!-- Request List -->
|
||||
<!-- Request List (Grouped by correlationId) -->
|
||||
${this.logs.length === 0 ? html`
|
||||
<div class="empty-state">No request logs found. Traffic will appear here as TypedRequests are made.</div>
|
||||
` : filteredLogs.length === 0 ? html`
|
||||
` : groupedLogs.length === 0 ? html`
|
||||
<div class="empty-state">No logs match filter</div>
|
||||
` : html`
|
||||
<div class="requests-list">
|
||||
${filteredLogs.map(log => html`
|
||||
<div class="request-card ${log.error ? 'has-error' : ''}">
|
||||
${groupedLogs.map(group => html`
|
||||
<div class="request-card ${group.hasError ? 'has-error' : ''}">
|
||||
<div class="request-header">
|
||||
<div>
|
||||
<div class="request-badges">
|
||||
<span class="badge direction-${log.direction}">${log.direction}</span>
|
||||
<span class="badge phase-${log.phase}">${log.phase}</span>
|
||||
${log.error ? html`<span class="badge error">error</span>` : ''}
|
||||
${group.request ? html`
|
||||
<span class="badge direction-${group.request.direction}">${group.request.direction}</span>
|
||||
` : ''}
|
||||
${group.hasError ? html`<span class="badge error">error</span>` : ''}
|
||||
</div>
|
||||
<div class="method-name">${group.method}</div>
|
||||
<div class="correlation-id">${group.correlationId}</div>
|
||||
<div class="request-response-badges">
|
||||
<span class="status-badge ${group.request ? 'has-request' : 'pending'}">
|
||||
${group.request ? 'REQ' : 'REQ pending'}
|
||||
</span>
|
||||
<span class="status-badge ${group.response ? 'has-response' : 'pending'}">
|
||||
${group.response ? 'RES' : 'RES pending'}
|
||||
</span>
|
||||
</div>
|
||||
<div class="method-name">${log.method}</div>
|
||||
<div class="correlation-id">${log.correlationId}</div>
|
||||
</div>
|
||||
<div class="request-meta">
|
||||
<span class="request-time">${this.formatTimestamp(log.timestamp)}</span>
|
||||
${log.durationMs !== undefined ? html`
|
||||
<span class="request-duration ${this.getDurationClass(log.durationMs)}">
|
||||
${this.formatDuration(log.durationMs)}
|
||||
<span class="request-time">${this.formatTimestamp(group.timestamp)}</span>
|
||||
${group.durationMs !== undefined ? html`
|
||||
<span class="request-duration ${this.getDurationClass(group.durationMs)}">
|
||||
${this.formatDuration(group.durationMs)}
|
||||
</span>
|
||||
` : ''}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${log.error ? html`
|
||||
<div class="request-error">${log.error}</div>
|
||||
${group.response?.error ? html`
|
||||
<div class="request-error">${group.response.error}</div>
|
||||
` : ''}
|
||||
|
||||
<div class="toggle-payload" @click="${() => this.togglePayload(log.correlationId)}">
|
||||
${this.expandedPayloads.has(log.correlationId) ? 'Hide payload' : 'Show payload'}
|
||||
</div>
|
||||
|
||||
${this.expandedPayloads.has(log.correlationId) ? html`
|
||||
<div class="request-payload">${JSON.stringify(log.payload, null, 2)}</div>
|
||||
` : ''}
|
||||
<button class="btn-show-payload" @click="${() => this.openPayloadModal(group)}">
|
||||
Show Payload
|
||||
</button>
|
||||
</div>
|
||||
`)}
|
||||
</div>
|
||||
|
||||
@@ -29,8 +29,22 @@ export class RequestLogStore {
|
||||
|
||||
/**
|
||||
* Add a new log entry
|
||||
* Rejects entries for serviceworker_* methods to prevent pollution from SW internal messages
|
||||
*/
|
||||
public addEntry(entry: interfaces.serviceworker.ITypedRequestLogEntry): void {
|
||||
// Reject serviceworker_* methods - these are internal SW messages, not app traffic
|
||||
// This prevents infinite loop pollution if hooks bypass somehow
|
||||
if (entry.method && entry.method.startsWith('serviceworker_')) {
|
||||
logger.log('note', `Rejecting serviceworker_* entry: ${entry.method}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Also reject entries with deeply nested payloads (sign of previous loop corruption)
|
||||
if (this.hasNestedServiceworkerPayload(entry)) {
|
||||
logger.log('warn', `Rejecting corrupted entry with nested serviceworker_* payload`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Add to log
|
||||
this.logs.push(entry);
|
||||
|
||||
@@ -43,6 +57,29 @@ export class RequestLogStore {
|
||||
this.updateStats(entry);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an entry has nested serviceworker_* methods in its payload (corruption from old loops)
|
||||
*/
|
||||
private hasNestedServiceworkerPayload(entry: interfaces.serviceworker.ITypedRequestLogEntry, depth = 0): boolean {
|
||||
// Limit recursion depth to prevent stack overflow
|
||||
if (depth > 3) return false;
|
||||
|
||||
const payload = entry.payload;
|
||||
if (!payload || typeof payload !== 'object') return false;
|
||||
|
||||
// Check if payload looks like a TypedRequest log entry with serviceworker_* method
|
||||
if (payload.method && typeof payload.method === 'string' && payload.method.startsWith('serviceworker_')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check nested payload
|
||||
if (payload.payload) {
|
||||
return this.hasNestedServiceworkerPayload({ ...entry, payload: payload.payload }, depth + 1);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update statistics based on new entry
|
||||
*/
|
||||
|
||||
@@ -202,6 +202,7 @@ export class ActionManager {
|
||||
public async logTypedRequest(entry: interfaces.serviceworker.ITypedRequestLogEntry): Promise<void> {
|
||||
try {
|
||||
const tr = this.deesComms.createTypedRequest<interfaces.serviceworker.IMessage_Serviceworker_TypedRequestLog>('serviceworker_typedRequestLog');
|
||||
tr.skipHooks = true; // Prevent infinite loops - don't log the logging request
|
||||
await tr.fire(entry);
|
||||
} catch (error) {
|
||||
// Silently ignore logging errors to avoid infinite loops
|
||||
|
||||
Reference in New Issue
Block a user