@marsaude/devtools-shell 0.1.5 → 0.1.6
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.
|
@@ -1645,6 +1645,282 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
1645
1645
|
type: Injectable
|
|
1646
1646
|
}] });
|
|
1647
1647
|
|
|
1648
|
+
/** Ring-buffer cap so a long QA session never grows unbounded. */
|
|
1649
|
+
const MAX_ENTRIES$1 = 300;
|
|
1650
|
+
/**
|
|
1651
|
+
* In-memory telemetry of every HTTP call to the API host (the one in
|
|
1652
|
+
* `apiBaseUrl`). Patches `fetch` and `XMLHttpRequest` once so it captures BOTH
|
|
1653
|
+
* the host app's requests and the package's own — independent of which
|
|
1654
|
+
* HttpClient/interceptor issued them. Nothing is persisted: a page reload
|
|
1655
|
+
* clears the log.
|
|
1656
|
+
*/
|
|
1657
|
+
class DevtoolsTelemetryService {
|
|
1658
|
+
constructor() {
|
|
1659
|
+
this.config = inject(DEVTOOLS_CONFIG);
|
|
1660
|
+
this._entries = signal([], ...(ngDevMode ? [{ debugName: "_entries" }] : []));
|
|
1661
|
+
this.entries = this._entries.asReadonly();
|
|
1662
|
+
this.installed = false;
|
|
1663
|
+
this.seq = 0;
|
|
1664
|
+
this.origin = this.deriveOrigin();
|
|
1665
|
+
}
|
|
1666
|
+
clear() {
|
|
1667
|
+
this._entries.set([]);
|
|
1668
|
+
}
|
|
1669
|
+
/** Monkeypatch fetch + XHR once. Safe to call multiple times. */
|
|
1670
|
+
install() {
|
|
1671
|
+
if (this.installed || typeof window === 'undefined')
|
|
1672
|
+
return;
|
|
1673
|
+
this.installed = true;
|
|
1674
|
+
this.patchFetch();
|
|
1675
|
+
this.patchXhr();
|
|
1676
|
+
}
|
|
1677
|
+
// ---- capture helpers --------------------------------------------------
|
|
1678
|
+
deriveOrigin() {
|
|
1679
|
+
try {
|
|
1680
|
+
return new URL(this.config.apiBaseUrl).origin;
|
|
1681
|
+
}
|
|
1682
|
+
catch {
|
|
1683
|
+
return this.config.apiBaseUrl;
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
matches(url) {
|
|
1687
|
+
try {
|
|
1688
|
+
return new URL(url, window.location.href).origin === this.origin;
|
|
1689
|
+
}
|
|
1690
|
+
catch {
|
|
1691
|
+
return url.includes(this.origin);
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
parseParams(url) {
|
|
1695
|
+
try {
|
|
1696
|
+
const out = {};
|
|
1697
|
+
new URL(url, window.location.href).searchParams.forEach((v, k) => (out[k] = v));
|
|
1698
|
+
return out;
|
|
1699
|
+
}
|
|
1700
|
+
catch {
|
|
1701
|
+
return {};
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
tryParse(text) {
|
|
1705
|
+
if (text == null || text === '')
|
|
1706
|
+
return null;
|
|
1707
|
+
try {
|
|
1708
|
+
return JSON.parse(text);
|
|
1709
|
+
}
|
|
1710
|
+
catch {
|
|
1711
|
+
return text;
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
normalizeBody(body) {
|
|
1715
|
+
if (body == null)
|
|
1716
|
+
return null;
|
|
1717
|
+
if (typeof body === 'string')
|
|
1718
|
+
return this.tryParse(body);
|
|
1719
|
+
if (typeof FormData !== 'undefined' && body instanceof FormData) {
|
|
1720
|
+
const out = {};
|
|
1721
|
+
body.forEach((v, k) => (out[k] = v instanceof File ? `[File ${v.name}]` : v));
|
|
1722
|
+
return out;
|
|
1723
|
+
}
|
|
1724
|
+
if (typeof Blob !== 'undefined' && body instanceof Blob)
|
|
1725
|
+
return `[Blob ${body.size}b]`;
|
|
1726
|
+
return body;
|
|
1727
|
+
}
|
|
1728
|
+
begin(method, url, body, source) {
|
|
1729
|
+
const entry = {
|
|
1730
|
+
id: ++this.seq,
|
|
1731
|
+
startedAt: Date.now(),
|
|
1732
|
+
method,
|
|
1733
|
+
url,
|
|
1734
|
+
params: this.parseParams(url),
|
|
1735
|
+
requestBody: this.normalizeBody(body),
|
|
1736
|
+
status: null,
|
|
1737
|
+
ok: null,
|
|
1738
|
+
responseBody: null,
|
|
1739
|
+
error: null,
|
|
1740
|
+
durationMs: null,
|
|
1741
|
+
pending: true,
|
|
1742
|
+
source,
|
|
1743
|
+
};
|
|
1744
|
+
this._entries.update((list) => [entry, ...list].slice(0, MAX_ENTRIES$1));
|
|
1745
|
+
return entry;
|
|
1746
|
+
}
|
|
1747
|
+
finish(id, patch) {
|
|
1748
|
+
this._entries.update((list) => list.map((e) => (e.id === id ? { ...e, ...patch, pending: false } : e)));
|
|
1749
|
+
}
|
|
1750
|
+
// ---- patches ----------------------------------------------------------
|
|
1751
|
+
patchFetch() {
|
|
1752
|
+
if (typeof window.fetch !== 'function')
|
|
1753
|
+
return;
|
|
1754
|
+
const orig = window.fetch.bind(window);
|
|
1755
|
+
const self = this;
|
|
1756
|
+
window.fetch = function (input, init) {
|
|
1757
|
+
const url = typeof input === 'string' ? input : input instanceof URL ? input.href : input.url;
|
|
1758
|
+
if (!self.matches(url))
|
|
1759
|
+
return orig(input, init);
|
|
1760
|
+
const method = (init?.method || (input instanceof Request ? input.method : 'GET')).toUpperCase();
|
|
1761
|
+
const entry = self.begin(method, url, init?.body, 'fetch');
|
|
1762
|
+
const t0 = performance.now();
|
|
1763
|
+
return orig(input, init).then((res) => {
|
|
1764
|
+
const durationMs = Math.round(performance.now() - t0);
|
|
1765
|
+
res
|
|
1766
|
+
.clone()
|
|
1767
|
+
.text()
|
|
1768
|
+
.then((text) => self.finish(entry.id, {
|
|
1769
|
+
status: res.status,
|
|
1770
|
+
ok: res.ok,
|
|
1771
|
+
responseBody: self.tryParse(text),
|
|
1772
|
+
error: res.ok ? null : `HTTP ${res.status}`,
|
|
1773
|
+
durationMs,
|
|
1774
|
+
}))
|
|
1775
|
+
.catch(() => self.finish(entry.id, { status: res.status, ok: res.ok, durationMs }));
|
|
1776
|
+
return res;
|
|
1777
|
+
}, (err) => {
|
|
1778
|
+
self.finish(entry.id, { error: String(err?.message ?? err), ok: false, durationMs: Math.round(performance.now() - t0) });
|
|
1779
|
+
throw err;
|
|
1780
|
+
});
|
|
1781
|
+
};
|
|
1782
|
+
}
|
|
1783
|
+
patchXhr() {
|
|
1784
|
+
if (typeof XMLHttpRequest === 'undefined')
|
|
1785
|
+
return;
|
|
1786
|
+
const proto = XMLHttpRequest.prototype;
|
|
1787
|
+
const origOpen = proto.open;
|
|
1788
|
+
const origSend = proto.send;
|
|
1789
|
+
const self = this;
|
|
1790
|
+
proto.open = function (method, url, ...rest) {
|
|
1791
|
+
this.__dtMeta = { method: String(method).toUpperCase(), url: String(url) };
|
|
1792
|
+
return origOpen.call(this, method, url, ...rest);
|
|
1793
|
+
};
|
|
1794
|
+
proto.send = function (body) {
|
|
1795
|
+
const meta = this.__dtMeta;
|
|
1796
|
+
if (meta && self.matches(meta.url)) {
|
|
1797
|
+
const entry = self.begin(meta.method, meta.url, body, 'xhr');
|
|
1798
|
+
const t0 = performance.now();
|
|
1799
|
+
this.addEventListener('loadend', () => {
|
|
1800
|
+
const durationMs = Math.round(performance.now() - t0);
|
|
1801
|
+
const status = this.status;
|
|
1802
|
+
if (status === 0) {
|
|
1803
|
+
self.finish(entry.id, { error: 'Network error / aborted', ok: false, durationMs });
|
|
1804
|
+
return;
|
|
1805
|
+
}
|
|
1806
|
+
const ok = status >= 200 && status < 300;
|
|
1807
|
+
let responseBody = null;
|
|
1808
|
+
try {
|
|
1809
|
+
const type = this.responseType;
|
|
1810
|
+
if (type === '' || type === 'text')
|
|
1811
|
+
responseBody = self.tryParse(this.responseText);
|
|
1812
|
+
else if (type === 'json')
|
|
1813
|
+
responseBody = this.response;
|
|
1814
|
+
else
|
|
1815
|
+
responseBody = `[${type}]`;
|
|
1816
|
+
}
|
|
1817
|
+
catch {
|
|
1818
|
+
/* ignore */
|
|
1819
|
+
}
|
|
1820
|
+
self.finish(entry.id, { status, ok, responseBody, error: ok ? null : `HTTP ${status}`, durationMs });
|
|
1821
|
+
});
|
|
1822
|
+
}
|
|
1823
|
+
return origSend.call(this, body);
|
|
1824
|
+
};
|
|
1825
|
+
}
|
|
1826
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DevtoolsTelemetryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1827
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DevtoolsTelemetryService }); }
|
|
1828
|
+
}
|
|
1829
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DevtoolsTelemetryService, decorators: [{
|
|
1830
|
+
type: Injectable
|
|
1831
|
+
}] });
|
|
1832
|
+
|
|
1833
|
+
/** Ring-buffer cap so a noisy session never grows unbounded. */
|
|
1834
|
+
const MAX_ENTRIES = 300;
|
|
1835
|
+
/**
|
|
1836
|
+
* In-memory log of runtime JS/TS errors: uncaught exceptions (`window.error`),
|
|
1837
|
+
* unhandled promise rejections, and anything sent to `console.error` (which is
|
|
1838
|
+
* where Angular's default ErrorHandler reports zone-caught errors). Captures
|
|
1839
|
+
* the host app's errors and the package's alike. Nothing is persisted — a page
|
|
1840
|
+
* reload clears it.
|
|
1841
|
+
*/
|
|
1842
|
+
class DevtoolsErrorsService {
|
|
1843
|
+
constructor() {
|
|
1844
|
+
this._entries = signal([], ...(ngDevMode ? [{ debugName: "_entries" }] : []));
|
|
1845
|
+
this.entries = this._entries.asReadonly();
|
|
1846
|
+
this.installed = false;
|
|
1847
|
+
this.seq = 0;
|
|
1848
|
+
}
|
|
1849
|
+
clear() {
|
|
1850
|
+
this._entries.set([]);
|
|
1851
|
+
}
|
|
1852
|
+
/** Attach the listeners + patch console.error once. Safe to call repeatedly. */
|
|
1853
|
+
install() {
|
|
1854
|
+
if (this.installed || typeof window === 'undefined')
|
|
1855
|
+
return;
|
|
1856
|
+
this.installed = true;
|
|
1857
|
+
window.addEventListener('error', (e) => {
|
|
1858
|
+
// Skip resource-load errors (img/script/link) — those carry no error/message.
|
|
1859
|
+
if (!e.error && !e.message)
|
|
1860
|
+
return;
|
|
1861
|
+
this.push('error', e.message || String(e.error), e.error?.stack ?? null, this.loc(e.filename, e.lineno, e.colno));
|
|
1862
|
+
});
|
|
1863
|
+
window.addEventListener('unhandledrejection', (e) => {
|
|
1864
|
+
const reason = e.reason;
|
|
1865
|
+
const message = reason instanceof Error ? reason.message : this.stringify(reason);
|
|
1866
|
+
this.push('rejection', message, reason?.stack ?? null, null);
|
|
1867
|
+
});
|
|
1868
|
+
const orig = console.error.bind(console);
|
|
1869
|
+
const self = this;
|
|
1870
|
+
console.error = function (...args) {
|
|
1871
|
+
orig(...args);
|
|
1872
|
+
try {
|
|
1873
|
+
self.captureConsole(args);
|
|
1874
|
+
}
|
|
1875
|
+
catch {
|
|
1876
|
+
/* never let telemetry break logging */
|
|
1877
|
+
}
|
|
1878
|
+
};
|
|
1879
|
+
}
|
|
1880
|
+
// ---- helpers ----------------------------------------------------------
|
|
1881
|
+
captureConsole(args) {
|
|
1882
|
+
const errArg = args.find((a) => a instanceof Error);
|
|
1883
|
+
const message = args
|
|
1884
|
+
.map((a) => (a instanceof Error ? a.message : typeof a === 'string' ? a : this.stringify(a)))
|
|
1885
|
+
.join(' ')
|
|
1886
|
+
.trim();
|
|
1887
|
+
if (!message)
|
|
1888
|
+
return;
|
|
1889
|
+
this.push('console', message, errArg?.stack ?? null, null);
|
|
1890
|
+
}
|
|
1891
|
+
loc(file, line, col) {
|
|
1892
|
+
if (!file)
|
|
1893
|
+
return null;
|
|
1894
|
+
return `${file}${line != null ? ':' + line : ''}${col != null ? ':' + col : ''}`;
|
|
1895
|
+
}
|
|
1896
|
+
stringify(v) {
|
|
1897
|
+
if (v == null)
|
|
1898
|
+
return String(v);
|
|
1899
|
+
if (typeof v === 'string')
|
|
1900
|
+
return v;
|
|
1901
|
+
try {
|
|
1902
|
+
return JSON.stringify(v);
|
|
1903
|
+
}
|
|
1904
|
+
catch {
|
|
1905
|
+
return String(v);
|
|
1906
|
+
}
|
|
1907
|
+
}
|
|
1908
|
+
push(kind, message, stack, source) {
|
|
1909
|
+
// Collapse an immediate duplicate (e.g. Angular ErrorHandler -> console.error
|
|
1910
|
+
// right after a window 'error' for the same throw).
|
|
1911
|
+
const last = this._entries()[0];
|
|
1912
|
+
if (last && last.message === message && last.stack === stack)
|
|
1913
|
+
return;
|
|
1914
|
+
const entry = { id: ++this.seq, at: Date.now(), kind, message, stack, source };
|
|
1915
|
+
this._entries.update((list) => [entry, ...list].slice(0, MAX_ENTRIES));
|
|
1916
|
+
}
|
|
1917
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DevtoolsErrorsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1918
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DevtoolsErrorsService }); }
|
|
1919
|
+
}
|
|
1920
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DevtoolsErrorsService, decorators: [{
|
|
1921
|
+
type: Injectable
|
|
1922
|
+
}] });
|
|
1923
|
+
|
|
1648
1924
|
/**
|
|
1649
1925
|
* Shared inline-style objects for the DevTools domain panels. Bound via
|
|
1650
1926
|
* `[style]="UI.x"` so there are no view-encapsulation surprises when the panels
|
|
@@ -3038,6 +3314,308 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
3038
3314
|
}]
|
|
3039
3315
|
}] });
|
|
3040
3316
|
|
|
3317
|
+
/**
|
|
3318
|
+
* Live, in-memory log of every API call (host app + package). Click a row to
|
|
3319
|
+
* inspect params/body/response/error. Cleared by a page reload or the button.
|
|
3320
|
+
*/
|
|
3321
|
+
class TelemetryPanelComponent {
|
|
3322
|
+
constructor() {
|
|
3323
|
+
this.UI = UI;
|
|
3324
|
+
this.telemetry = inject(DevtoolsTelemetryService);
|
|
3325
|
+
this.entries = this.telemetry.entries;
|
|
3326
|
+
this.count = computed(() => this.entries().length, ...(ngDevMode ? [{ debugName: "count" }] : []));
|
|
3327
|
+
this.expanded = signal(null, ...(ngDevMode ? [{ debugName: "expanded" }] : []));
|
|
3328
|
+
this.badge = {
|
|
3329
|
+
fontFamily: "'JetBrains Mono',monospace",
|
|
3330
|
+
fontSize: '9.5px',
|
|
3331
|
+
fontWeight: '700',
|
|
3332
|
+
letterSpacing: '.03em',
|
|
3333
|
+
padding: '2px 6px',
|
|
3334
|
+
borderRadius: '6px',
|
|
3335
|
+
background: '#23252f',
|
|
3336
|
+
color: '#c4c7d0',
|
|
3337
|
+
flex: 'none',
|
|
3338
|
+
};
|
|
3339
|
+
}
|
|
3340
|
+
toggle(id) {
|
|
3341
|
+
this.expanded.update((cur) => (cur === id ? null : id));
|
|
3342
|
+
}
|
|
3343
|
+
clear() {
|
|
3344
|
+
this.telemetry.clear();
|
|
3345
|
+
this.expanded.set(null);
|
|
3346
|
+
}
|
|
3347
|
+
path(url) {
|
|
3348
|
+
try {
|
|
3349
|
+
const u = new URL(url, window.location.href);
|
|
3350
|
+
return u.pathname + u.search;
|
|
3351
|
+
}
|
|
3352
|
+
catch {
|
|
3353
|
+
return url;
|
|
3354
|
+
}
|
|
3355
|
+
}
|
|
3356
|
+
hasParams(e) {
|
|
3357
|
+
return Object.keys(e.params).length > 0;
|
|
3358
|
+
}
|
|
3359
|
+
json(v) {
|
|
3360
|
+
try {
|
|
3361
|
+
return typeof v === 'string' ? v : JSON.stringify(v, null, 2);
|
|
3362
|
+
}
|
|
3363
|
+
catch {
|
|
3364
|
+
return String(v);
|
|
3365
|
+
}
|
|
3366
|
+
}
|
|
3367
|
+
statusColor(e) {
|
|
3368
|
+
if (e.pending)
|
|
3369
|
+
return '#ffc454';
|
|
3370
|
+
if (e.error || (e.status ?? 0) >= 400)
|
|
3371
|
+
return '#ff6b6b';
|
|
3372
|
+
return '#1fbe7e';
|
|
3373
|
+
}
|
|
3374
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: TelemetryPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
3375
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: TelemetryPanelComponent, isStandalone: true, selector: "devtools-telemetry-panel", ngImport: i0, template: `
|
|
3376
|
+
<p [style]="UI.intro" style="margin-bottom:10px">
|
|
3377
|
+
Todas as chamadas à API ({{ count() }}). Em memória — o reload limpa.
|
|
3378
|
+
</p>
|
|
3379
|
+
<button type="button" [style]="UI.ghost" style="margin-top:0;margin-bottom:14px" (click)="clear()">
|
|
3380
|
+
<span class="dts-sym" style="font-family:'Material Symbols Outlined';font-size:17px;vertical-align:-3px">delete_sweep</span>
|
|
3381
|
+
Limpar log
|
|
3382
|
+
</button>
|
|
3383
|
+
|
|
3384
|
+
@if (!count()) {
|
|
3385
|
+
<p [style]="UI.mono">Nenhuma requisição capturada ainda.</p>
|
|
3386
|
+
}
|
|
3387
|
+
|
|
3388
|
+
@for (e of entries(); track e.id) {
|
|
3389
|
+
<div [style]="UI.card" style="margin-bottom:8px;cursor:pointer;padding:11px 13px" (click)="toggle(e.id)">
|
|
3390
|
+
<div style="display:flex;align-items:center;gap:8px">
|
|
3391
|
+
<span [style]="badge">{{ e.method }}</span>
|
|
3392
|
+
<span style="font-size:11px;font-weight:700;min-width:32px" [style.color]="statusColor(e)">
|
|
3393
|
+
{{ e.pending ? '···' : (e.status ?? 'ERR') }}
|
|
3394
|
+
</span>
|
|
3395
|
+
<span [style]="UI.mono" style="flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis">
|
|
3396
|
+
{{ path(e.url) }}
|
|
3397
|
+
</span>
|
|
3398
|
+
@if (e.durationMs != null) {
|
|
3399
|
+
<span [style]="UI.mono" style="white-space:nowrap;color:#6a6e7b">{{ e.durationMs }}ms</span>
|
|
3400
|
+
}
|
|
3401
|
+
</div>
|
|
3402
|
+
|
|
3403
|
+
@if (expanded() === e.id) {
|
|
3404
|
+
<div style="margin-top:11px;display:flex;flex-direction:column;gap:7px" (click)="$event.stopPropagation()">
|
|
3405
|
+
<div [style]="UI.label">URL</div>
|
|
3406
|
+
<pre [style]="UI.pre">{{ e.url }}</pre>
|
|
3407
|
+
|
|
3408
|
+
@if (hasParams(e)) {
|
|
3409
|
+
<div [style]="UI.label">Query params</div>
|
|
3410
|
+
<pre [style]="UI.pre">{{ json(e.params) }}</pre>
|
|
3411
|
+
}
|
|
3412
|
+
@if (e.requestBody !== null) {
|
|
3413
|
+
<div [style]="UI.label">Request body</div>
|
|
3414
|
+
<pre [style]="UI.pre">{{ json(e.requestBody) }}</pre>
|
|
3415
|
+
}
|
|
3416
|
+
@if (e.error) {
|
|
3417
|
+
<div [style]="UI.label" style="color:#ff7676">Erro</div>
|
|
3418
|
+
<pre [style]="UI.pre" style="color:#ff9b9b;border-color:#5a2a2a">{{ e.error }}</pre>
|
|
3419
|
+
}
|
|
3420
|
+
@if (e.responseBody !== null) {
|
|
3421
|
+
<div [style]="UI.label">Response</div>
|
|
3422
|
+
<pre [style]="UI.pre">{{ json(e.responseBody) }}</pre>
|
|
3423
|
+
}
|
|
3424
|
+
</div>
|
|
3425
|
+
}
|
|
3426
|
+
</div>
|
|
3427
|
+
}
|
|
3428
|
+
`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
3429
|
+
}
|
|
3430
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: TelemetryPanelComponent, decorators: [{
|
|
3431
|
+
type: Component,
|
|
3432
|
+
args: [{
|
|
3433
|
+
selector: 'devtools-telemetry-panel',
|
|
3434
|
+
standalone: true,
|
|
3435
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
3436
|
+
template: `
|
|
3437
|
+
<p [style]="UI.intro" style="margin-bottom:10px">
|
|
3438
|
+
Todas as chamadas à API ({{ count() }}). Em memória — o reload limpa.
|
|
3439
|
+
</p>
|
|
3440
|
+
<button type="button" [style]="UI.ghost" style="margin-top:0;margin-bottom:14px" (click)="clear()">
|
|
3441
|
+
<span class="dts-sym" style="font-family:'Material Symbols Outlined';font-size:17px;vertical-align:-3px">delete_sweep</span>
|
|
3442
|
+
Limpar log
|
|
3443
|
+
</button>
|
|
3444
|
+
|
|
3445
|
+
@if (!count()) {
|
|
3446
|
+
<p [style]="UI.mono">Nenhuma requisição capturada ainda.</p>
|
|
3447
|
+
}
|
|
3448
|
+
|
|
3449
|
+
@for (e of entries(); track e.id) {
|
|
3450
|
+
<div [style]="UI.card" style="margin-bottom:8px;cursor:pointer;padding:11px 13px" (click)="toggle(e.id)">
|
|
3451
|
+
<div style="display:flex;align-items:center;gap:8px">
|
|
3452
|
+
<span [style]="badge">{{ e.method }}</span>
|
|
3453
|
+
<span style="font-size:11px;font-weight:700;min-width:32px" [style.color]="statusColor(e)">
|
|
3454
|
+
{{ e.pending ? '···' : (e.status ?? 'ERR') }}
|
|
3455
|
+
</span>
|
|
3456
|
+
<span [style]="UI.mono" style="flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis">
|
|
3457
|
+
{{ path(e.url) }}
|
|
3458
|
+
</span>
|
|
3459
|
+
@if (e.durationMs != null) {
|
|
3460
|
+
<span [style]="UI.mono" style="white-space:nowrap;color:#6a6e7b">{{ e.durationMs }}ms</span>
|
|
3461
|
+
}
|
|
3462
|
+
</div>
|
|
3463
|
+
|
|
3464
|
+
@if (expanded() === e.id) {
|
|
3465
|
+
<div style="margin-top:11px;display:flex;flex-direction:column;gap:7px" (click)="$event.stopPropagation()">
|
|
3466
|
+
<div [style]="UI.label">URL</div>
|
|
3467
|
+
<pre [style]="UI.pre">{{ e.url }}</pre>
|
|
3468
|
+
|
|
3469
|
+
@if (hasParams(e)) {
|
|
3470
|
+
<div [style]="UI.label">Query params</div>
|
|
3471
|
+
<pre [style]="UI.pre">{{ json(e.params) }}</pre>
|
|
3472
|
+
}
|
|
3473
|
+
@if (e.requestBody !== null) {
|
|
3474
|
+
<div [style]="UI.label">Request body</div>
|
|
3475
|
+
<pre [style]="UI.pre">{{ json(e.requestBody) }}</pre>
|
|
3476
|
+
}
|
|
3477
|
+
@if (e.error) {
|
|
3478
|
+
<div [style]="UI.label" style="color:#ff7676">Erro</div>
|
|
3479
|
+
<pre [style]="UI.pre" style="color:#ff9b9b;border-color:#5a2a2a">{{ e.error }}</pre>
|
|
3480
|
+
}
|
|
3481
|
+
@if (e.responseBody !== null) {
|
|
3482
|
+
<div [style]="UI.label">Response</div>
|
|
3483
|
+
<pre [style]="UI.pre">{{ json(e.responseBody) }}</pre>
|
|
3484
|
+
}
|
|
3485
|
+
</div>
|
|
3486
|
+
}
|
|
3487
|
+
</div>
|
|
3488
|
+
}
|
|
3489
|
+
`,
|
|
3490
|
+
}]
|
|
3491
|
+
}] });
|
|
3492
|
+
|
|
3493
|
+
/**
|
|
3494
|
+
* Live, in-memory log of runtime JS/TS errors (uncaught, unhandled rejections,
|
|
3495
|
+
* console.error). Click a row to see the full stack. Cleared by a reload.
|
|
3496
|
+
*/
|
|
3497
|
+
class ErrorsPanelComponent {
|
|
3498
|
+
constructor() {
|
|
3499
|
+
this.UI = UI;
|
|
3500
|
+
this.errors = inject(DevtoolsErrorsService);
|
|
3501
|
+
this.entries = this.errors.entries;
|
|
3502
|
+
this.count = computed(() => this.entries().length, ...(ngDevMode ? [{ debugName: "count" }] : []));
|
|
3503
|
+
this.expanded = signal(null, ...(ngDevMode ? [{ debugName: "expanded" }] : []));
|
|
3504
|
+
this.badge = {
|
|
3505
|
+
fontFamily: "'JetBrains Mono',monospace",
|
|
3506
|
+
fontSize: '9.5px',
|
|
3507
|
+
fontWeight: '700',
|
|
3508
|
+
letterSpacing: '.03em',
|
|
3509
|
+
padding: '2px 6px',
|
|
3510
|
+
borderRadius: '6px',
|
|
3511
|
+
background: '#2a2024',
|
|
3512
|
+
flex: 'none',
|
|
3513
|
+
};
|
|
3514
|
+
}
|
|
3515
|
+
toggle(id) {
|
|
3516
|
+
this.expanded.update((cur) => (cur === id ? null : id));
|
|
3517
|
+
}
|
|
3518
|
+
clear() {
|
|
3519
|
+
this.errors.clear();
|
|
3520
|
+
this.expanded.set(null);
|
|
3521
|
+
}
|
|
3522
|
+
kindLabel(kind) {
|
|
3523
|
+
return kind === 'rejection' ? 'PROMISE' : kind === 'console' ? 'CONSOLE' : 'ERROR';
|
|
3524
|
+
}
|
|
3525
|
+
kindColor(kind) {
|
|
3526
|
+
return kind === 'console' ? '#ffb454' : '#ff6b6b';
|
|
3527
|
+
}
|
|
3528
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ErrorsPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
3529
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: ErrorsPanelComponent, isStandalone: true, selector: "devtools-errors-panel", ngImport: i0, template: `
|
|
3530
|
+
<p [style]="UI.intro" style="margin-bottom:10px">
|
|
3531
|
+
Erros de runtime JS/TS ({{ count() }}). Em memória — o reload limpa.
|
|
3532
|
+
</p>
|
|
3533
|
+
<button type="button" [style]="UI.ghost" style="margin-top:0;margin-bottom:14px" (click)="clear()">
|
|
3534
|
+
<span class="dts-sym" style="font-family:'Material Symbols Outlined';font-size:17px;vertical-align:-3px">delete_sweep</span>
|
|
3535
|
+
Limpar log
|
|
3536
|
+
</button>
|
|
3537
|
+
|
|
3538
|
+
@if (!count()) {
|
|
3539
|
+
<p [style]="UI.mono">Nenhum erro capturado. 🎉</p>
|
|
3540
|
+
}
|
|
3541
|
+
|
|
3542
|
+
@for (e of entries(); track e.id) {
|
|
3543
|
+
<div [style]="UI.card" style="margin-bottom:8px;cursor:pointer;padding:11px 13px;border-color:#3a2a2a" (click)="toggle(e.id)">
|
|
3544
|
+
<div style="display:flex;align-items:center;gap:8px">
|
|
3545
|
+
<span [style]="badge" [style.color]="kindColor(e.kind)">{{ kindLabel(e.kind) }}</span>
|
|
3546
|
+
<span style="flex:1;min-width:0;font-size:12.5px;color:#f0d6d6;white-space:nowrap;overflow:hidden;text-overflow:ellipsis">
|
|
3547
|
+
{{ e.message }}
|
|
3548
|
+
</span>
|
|
3549
|
+
</div>
|
|
3550
|
+
|
|
3551
|
+
@if (expanded() === e.id) {
|
|
3552
|
+
<div style="margin-top:11px;display:flex;flex-direction:column;gap:7px" (click)="$event.stopPropagation()">
|
|
3553
|
+
<div [style]="UI.label">Mensagem</div>
|
|
3554
|
+
<pre [style]="UI.pre" style="color:#ff9b9b;border-color:#5a2a2a">{{ e.message }}</pre>
|
|
3555
|
+
|
|
3556
|
+
@if (e.source) {
|
|
3557
|
+
<div [style]="UI.label">Origem</div>
|
|
3558
|
+
<pre [style]="UI.pre">{{ e.source }}</pre>
|
|
3559
|
+
}
|
|
3560
|
+
@if (e.stack) {
|
|
3561
|
+
<div [style]="UI.label">Stack</div>
|
|
3562
|
+
<pre [style]="UI.pre">{{ e.stack }}</pre>
|
|
3563
|
+
}
|
|
3564
|
+
</div>
|
|
3565
|
+
}
|
|
3566
|
+
</div>
|
|
3567
|
+
}
|
|
3568
|
+
`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
3569
|
+
}
|
|
3570
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ErrorsPanelComponent, decorators: [{
|
|
3571
|
+
type: Component,
|
|
3572
|
+
args: [{
|
|
3573
|
+
selector: 'devtools-errors-panel',
|
|
3574
|
+
standalone: true,
|
|
3575
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
3576
|
+
template: `
|
|
3577
|
+
<p [style]="UI.intro" style="margin-bottom:10px">
|
|
3578
|
+
Erros de runtime JS/TS ({{ count() }}). Em memória — o reload limpa.
|
|
3579
|
+
</p>
|
|
3580
|
+
<button type="button" [style]="UI.ghost" style="margin-top:0;margin-bottom:14px" (click)="clear()">
|
|
3581
|
+
<span class="dts-sym" style="font-family:'Material Symbols Outlined';font-size:17px;vertical-align:-3px">delete_sweep</span>
|
|
3582
|
+
Limpar log
|
|
3583
|
+
</button>
|
|
3584
|
+
|
|
3585
|
+
@if (!count()) {
|
|
3586
|
+
<p [style]="UI.mono">Nenhum erro capturado. 🎉</p>
|
|
3587
|
+
}
|
|
3588
|
+
|
|
3589
|
+
@for (e of entries(); track e.id) {
|
|
3590
|
+
<div [style]="UI.card" style="margin-bottom:8px;cursor:pointer;padding:11px 13px;border-color:#3a2a2a" (click)="toggle(e.id)">
|
|
3591
|
+
<div style="display:flex;align-items:center;gap:8px">
|
|
3592
|
+
<span [style]="badge" [style.color]="kindColor(e.kind)">{{ kindLabel(e.kind) }}</span>
|
|
3593
|
+
<span style="flex:1;min-width:0;font-size:12.5px;color:#f0d6d6;white-space:nowrap;overflow:hidden;text-overflow:ellipsis">
|
|
3594
|
+
{{ e.message }}
|
|
3595
|
+
</span>
|
|
3596
|
+
</div>
|
|
3597
|
+
|
|
3598
|
+
@if (expanded() === e.id) {
|
|
3599
|
+
<div style="margin-top:11px;display:flex;flex-direction:column;gap:7px" (click)="$event.stopPropagation()">
|
|
3600
|
+
<div [style]="UI.label">Mensagem</div>
|
|
3601
|
+
<pre [style]="UI.pre" style="color:#ff9b9b;border-color:#5a2a2a">{{ e.message }}</pre>
|
|
3602
|
+
|
|
3603
|
+
@if (e.source) {
|
|
3604
|
+
<div [style]="UI.label">Origem</div>
|
|
3605
|
+
<pre [style]="UI.pre">{{ e.source }}</pre>
|
|
3606
|
+
}
|
|
3607
|
+
@if (e.stack) {
|
|
3608
|
+
<div [style]="UI.label">Stack</div>
|
|
3609
|
+
<pre [style]="UI.pre">{{ e.stack }}</pre>
|
|
3610
|
+
}
|
|
3611
|
+
</div>
|
|
3612
|
+
}
|
|
3613
|
+
</div>
|
|
3614
|
+
}
|
|
3615
|
+
`,
|
|
3616
|
+
}]
|
|
3617
|
+
}] });
|
|
3618
|
+
|
|
3041
3619
|
const BUILTIN_ACTIONS = [
|
|
3042
3620
|
{ id: 'admin', label: 'Admin', icon: 'shield_person', content: AdminLoginPanelComponent, order: 0 },
|
|
3043
3621
|
{ id: 'app-login', label: 'Login App', icon: 'login', content: LoginPanelComponent, order: 1 },
|
|
@@ -3047,6 +3625,8 @@ const BUILTIN_ACTIONS = [
|
|
|
3047
3625
|
{ id: 'exams', label: 'Exames', icon: 'biotech', content: ExamsPanelComponent, order: 5 },
|
|
3048
3626
|
{ id: 'data', label: 'Dados', icon: 'badge', content: DataPanelComponent, order: 6 },
|
|
3049
3627
|
{ id: 'copy', label: 'Copiar', icon: 'content_copy', content: CopyPanelComponent, order: 7 },
|
|
3628
|
+
{ id: 'telemetry', label: 'Telemetria', icon: 'monitoring', content: TelemetryPanelComponent, order: 8 },
|
|
3629
|
+
{ id: 'errors', label: 'Erros', icon: 'bug_report', content: ErrorsPanelComponent, order: 9 },
|
|
3050
3630
|
];
|
|
3051
3631
|
/**
|
|
3052
3632
|
* Mount the self-contained DevTools. Add to `bootstrapApplication` providers or
|
|
@@ -3110,11 +3690,18 @@ function mountIsolated(parent, config, actions, shell) {
|
|
|
3110
3690
|
DevtoolsApiService,
|
|
3111
3691
|
DevtoolsAuthService,
|
|
3112
3692
|
DevtoolsSessionService,
|
|
3693
|
+
DevtoolsTelemetryService,
|
|
3694
|
+
DevtoolsErrorsService,
|
|
3113
3695
|
DevtoolsPositionService,
|
|
3114
3696
|
DevtoolsToastService,
|
|
3115
3697
|
], parent);
|
|
3116
|
-
|
|
3117
|
-
|
|
3698
|
+
runInInjectionContext(injector, () => {
|
|
3699
|
+
// Start capturing API traffic + runtime errors ASAP, then consume our own
|
|
3700
|
+
// Google OAuth return (guarded) to finish admin login.
|
|
3701
|
+
inject(DevtoolsTelemetryService).install();
|
|
3702
|
+
inject(DevtoolsErrorsService).install();
|
|
3703
|
+
inject(DevtoolsAuthService).handleGoogleReturn();
|
|
3704
|
+
});
|
|
3118
3705
|
const appRef = parent.get(ApplicationRef);
|
|
3119
3706
|
const host = document.createElement('div');
|
|
3120
3707
|
host.setAttribute('data-devtools-shell-host', '');
|