catalyst-core 0.2.0-beta.3 → 0.3.0-beta.1
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.
- package/babel.native.config.cjs +8 -0
- package/bin/catalyst.js +49 -105
- package/changelog.md +8 -0
- package/dist/caching.js +1 -1
- package/dist/index.jsx +8 -0
- package/dist/logger.js +74 -3
- package/dist/native/androidSetup.js +300 -8
- package/dist/native/bridge/WebBridge.js +481 -59
- package/dist/native/bridge/constants/NativeInterfaces.js +290 -45
- package/dist/native/bridge/errors.js +250 -26
- package/dist/native/bridge/hooks/index.js +125 -1
- package/dist/native/bridge/hooks/useCamera.js +347 -9
- package/dist/native/bridge/hooks/useDataProtection.js +200 -2
- package/dist/native/bridge/hooks/useDeviceInfo.js +71 -2
- package/dist/native/bridge/hooks/useFilePicker.js +429 -2
- package/dist/native/bridge/hooks/useGoogleSignIn.js +81 -1
- package/dist/native/bridge/hooks/useHapticFeedback.js +196 -1
- package/dist/native/bridge/hooks/useIntent.js +95 -1
- package/dist/native/bridge/hooks/useNativeTransition.js +202 -27
- package/dist/native/bridge/hooks/useNetworkStatus.js +84 -3
- package/dist/native/bridge/hooks/useNotification.js +318 -2
- package/dist/native/bridge/hooks/useSafeArea.js +84 -2
- package/dist/native/bridge/hooks/useVideoStream.js +434 -6
- package/dist/native/bridge/hooks/utils.js +27 -1
- package/dist/native/bridge/hooks.js +16 -1
- package/dist/native/bridge/perf/collectors/ApiCollector.js +66 -3
- package/dist/native/bridge/perf/collectors/CacheCollector.js +186 -11
- package/dist/native/bridge/perf/collectors/HookCollector.js +141 -19
- package/dist/native/bridge/perf/collectors/InsightsCollector.js +481 -23
- package/dist/native/bridge/perf/collectors/InteractionCollector.js +245 -10
- package/dist/native/bridge/perf/collectors/KeyboardCollector.js +107 -8
- package/dist/native/bridge/perf/collectors/NavigationCollector.js +273 -32
- package/dist/native/bridge/perf/collectors/NetworkTimingCollector.js +178 -2
- package/dist/native/bridge/perf/collectors/RenderCollector.js +258 -14
- package/dist/native/bridge/perf/collectors/ScrollCollector.js +95 -9
- package/dist/native/bridge/perf/collectors/ThreadCollector.js +90 -6
- package/dist/native/bridge/perf/core/constants.js +103 -20
- package/dist/native/bridge/perf/core/measure.js +179 -30
- package/dist/native/bridge/perf/core/session.js +89 -23
- package/dist/native/bridge/perf/core/store.js +554 -1
- package/dist/native/bridge/perf/core/summary.js +174 -1
- package/dist/native/bridge/perf/core/traceExport.js +240 -1
- package/dist/native/bridge/perf/core/waterfalls.js +146 -1
- package/dist/native/bridge/perf/index.js +359 -38
- package/dist/native/bridge/safeArea.js +53 -5
- package/dist/native/bridge/standardizedHooks.js +595 -82
- package/dist/native/bridge/useBaseHook.js +274 -38
- package/dist/native/bridge/utils/CameraUtils.js +201 -56
- package/dist/native/bridge/utils/FileObjectConverter.js +129 -18
- package/dist/native/bridge/utils/NativeBridge.js +425 -141
- package/dist/native/buildAndroid/assets.js +510 -13
- package/dist/native/buildAndroid/build.js +296 -19
- package/dist/native/buildAndroid/config.js +87 -1
- package/dist/native/buildAndroid/index.js +374 -1
- package/dist/native/buildAppAndroid.js +34 -2
- package/dist/native/buildAppIos.js +62 -2
- package/dist/native/buildIos/assets.js +609 -8
- package/dist/native/buildIos/build.js +639 -20
- package/dist/native/buildIos/config.js +294 -12
- package/dist/native/buildIos/index.js +343 -6
- package/dist/native/buildIos/plugins.js +343 -16
- package/dist/native/internal-plugins/device-info-plugin/android/DeviceInfoPlugin.kt +43 -0
- package/dist/native/internal-plugins/device-info-plugin/ios/DeviceInfoPlugin.swift +28 -0
- package/dist/native/internal-plugins/device-info-plugin/manifest.json +19 -0
- package/dist/native/internalPluginUtils.js +232 -7
- package/dist/native/package.json +3 -0
- package/dist/native/plugin-bridge/PluginBridge.js +227 -1
- package/dist/native/pluginComposerAndroid.js +407 -23
- package/dist/native/pluginComposerIos.js +435 -11
- package/dist/native/renameAndroidProject.js +1094 -88
- package/dist/native/setupEmulatorIos.js +289 -22
- package/dist/native/setupServer.js +160 -19
- package/dist/native/terminalProgress.js +199 -11
- package/dist/native/utils.js +193 -15
- package/dist/offline/catalyst-sw.js +145 -1
- package/dist/offline/registerServiceWorker.js +33 -1
- package/dist/otel.js +375 -46
- package/dist/router/ClientRouter.js +14 -2
- package/dist/router/ServerRouter.js +9 -1
- package/dist/router.js +1 -1
- package/dist/scripts/build.js +112 -3
- package/dist/scripts/checkVersion.js +3 -1
- package/dist/scripts/cleanCache.js +30 -1
- package/dist/scripts/generateOfflineManifest.js +116 -1
- package/dist/scripts/loadEnvironmentVariables.js +32 -6
- package/dist/scripts/plugins.js +309 -1
- package/dist/scripts/preServerInit.js +13 -0
- package/dist/scripts/scriptUtils.js +54 -3
- package/dist/scripts/serve.js +51 -4
- package/dist/scripts/start.js +47 -10
- package/dist/scripts/validator.js +149 -1
- package/dist/sentry.cjs +127 -0
- package/dist/sentry.js +125 -5
- package/dist/server/expressServer.js +250 -5
- package/dist/server/manifestCache.js +36 -0
- package/dist/server/renderer/ChunkExtractor.js +144 -0
- package/dist/server/renderer/document/Body.jsx +59 -0
- package/dist/server/renderer/document/FastRefresh.jsx +20 -0
- package/dist/server/renderer/document/Head.jsx +60 -0
- package/dist/server/renderer/document/index.jsx +2 -0
- package/dist/server/renderer/extract.js +149 -31
- package/dist/server/renderer/handler.jsx +433 -0
- package/dist/server/renderer/index.js +8 -1
- package/dist/server/renderer/render.js +47 -0
- package/dist/server/utils/botDetectionMiddleware.js +29 -0
- package/dist/server/utils/cjsRequire.js +10 -0
- package/dist/server/utils/userAgentUtil.js +87 -9
- package/dist/server/utils/validator.js +164 -1
- package/dist/vite/FastRefresh.jsx +20 -0
- package/dist/vite/inject-cache-key-plugin.js +276 -0
- package/dist/vite/loadCustomViteConfig.js +22 -0
- package/dist/vite/manifest-categorization-plugin.js +455 -0
- package/dist/vite/node-loader.mjs +458 -0
- package/dist/vite/scssParams.js +21 -0
- package/dist/vite/vite.config.client.js +130 -0
- package/dist/vite/vite.config.js +422 -0
- package/dist/vite/vite.config.server.js +84 -0
- package/dist/web-router/components/MetaTag.jsx +34 -0
- package/dist/web-router/components/RouterDataProvider.jsx +332 -0
- package/dist/web-router/components/Split.jsx +200 -0
- package/dist/web-router/components/SplitInview.jsx +106 -0
- package/dist/web-router/components/SsrRequestContext.jsx +14 -0
- package/dist/web-router/context.jsx +12 -0
- package/dist/web-router/hooks.jsx +72 -0
- package/dist/web-router/types/components/MetaTag.d.ts +2 -0
- package/dist/web-router/types/components/RouterDataProvider.d.ts +95 -0
- package/dist/web-router/types/context.d.ts +14 -0
- package/dist/web-router/types/hooks.d.ts +1 -0
- package/dist/web-router/types/index.d.ts +3 -0
- package/dist/web-router/types/utils/metaDataUtils.d.ts +24 -0
- package/dist/web-router/utils/metaDataUtils.jsx +119 -0
- package/mcp_v2/conversion-tasks.json +35 -17
- package/mcp_v2/knowledge-base.json +124 -13
- package/mcp_v2/lib/helpers.js +10 -0
- package/mcp_v2/mcp.js +4 -4
- package/mcp_v2/package.json +1 -0
- package/mcp_v2/tools/build.js +116 -20
- package/mcp_v2/tools/conversion.js +106 -5
- package/mcp_v2/tools/tasks.js +18 -14
- package/package.json +35 -68
- package/babel.config.js +0 -32
- package/dist/index.js +0 -1
- package/dist/scripts/devBuild.js +0 -3
- package/dist/scripts/devServe.js +0 -3
- package/dist/scripts/loadScriptsBeforeServerStarts.js +0 -4
- package/dist/scripts/registerAliases.js +0 -6
- package/dist/server/renderer/document/Body.js +0 -11
- package/dist/server/renderer/document/Head.js +0 -9
- package/dist/server/renderer/document/index.js +0 -1
- package/dist/server/renderer/handler.js +0 -47
- package/dist/server/startServer.js +0 -57
- package/dist/webpack/babel-plugins/remove-client.plugin.js +0 -5
- package/dist/webpack/babel-plugins/remove-ssr.plugin.js +0 -5
- package/dist/webpack/babel.config.client.js +0 -1
- package/dist/webpack/babel.config.ssr.js +0 -1
- package/dist/webpack/base.babel.js +0 -7
- package/dist/webpack/development.client.babel.js +0 -10
- package/dist/webpack/production.client.babel.js +0 -4
- package/dist/webpack/production.ssr.babel.js +0 -8
- package/dist/webpack/scssParams.js +0 -9
|
@@ -1 +1,174 @@
|
|
|
1
|
-
"use strict";
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.buildSummary = buildSummary;
|
|
7
|
+
const SLOW_MS = {
|
|
8
|
+
interaction: 300,
|
|
9
|
+
request: 500,
|
|
10
|
+
nativeApi: 150,
|
|
11
|
+
render: 50
|
|
12
|
+
};
|
|
13
|
+
function round(value) {
|
|
14
|
+
return Number.isFinite(value) ? Math.round(value) : value;
|
|
15
|
+
}
|
|
16
|
+
function durationOf(record) {
|
|
17
|
+
if (Number.isFinite(record?.durationMs)) return record.durationMs;
|
|
18
|
+
if (Number.isFinite(record?.startTime) && Number.isFinite(record?.endTime)) {
|
|
19
|
+
return Math.max(0, record.endTime - record.startTime);
|
|
20
|
+
}
|
|
21
|
+
return 0;
|
|
22
|
+
}
|
|
23
|
+
function byDurationDesc(a, b) {
|
|
24
|
+
return durationOf(b) - durationOf(a);
|
|
25
|
+
}
|
|
26
|
+
function overlaps(record, start, end, padding = 0) {
|
|
27
|
+
if (!Number.isFinite(start) || !Number.isFinite(end)) return false;
|
|
28
|
+
const recordStart = record.startTime ?? 0;
|
|
29
|
+
const recordEnd = record.endTime ?? recordStart;
|
|
30
|
+
return recordStart < end + padding && recordEnd >= start - padding;
|
|
31
|
+
}
|
|
32
|
+
function top(records, count = 5) {
|
|
33
|
+
return [...records].sort(byDurationDesc).slice(0, count);
|
|
34
|
+
}
|
|
35
|
+
function countBy(records, key) {
|
|
36
|
+
return records.reduce((acc, record) => {
|
|
37
|
+
const value = record?.[key] ?? "unknown";
|
|
38
|
+
acc[value] = (acc[value] ?? 0) + 1;
|
|
39
|
+
return acc;
|
|
40
|
+
}, {});
|
|
41
|
+
}
|
|
42
|
+
function summarizeInteraction(interaction, data) {
|
|
43
|
+
const start = interaction.startTime;
|
|
44
|
+
const end = interaction.endTime;
|
|
45
|
+
const sessionId = interaction.sessionId ?? interaction.detail?.sessionId;
|
|
46
|
+
const related = (record, padding = 150) => {
|
|
47
|
+
const detail = record.detail ?? {};
|
|
48
|
+
return detail.sessionId === sessionId || detail.interactionId === sessionId || record.sessionId === sessionId || record.interactionId === sessionId || overlaps(record, start, end, padding);
|
|
49
|
+
};
|
|
50
|
+
const requests = data.requests.filter(request => related(request));
|
|
51
|
+
const render = data.metrics.filter(metric => ["fps-drop", "long-task", "loaf", "layout-shift"].includes(metric.kind)).filter(metric => related(metric, 300));
|
|
52
|
+
const insights = data.insights.filter(insight => related(insight, 300));
|
|
53
|
+
const nativeApiTime = requests.filter(request => request.kind === "native-api").reduce((total, request) => total + durationOf(request), 0);
|
|
54
|
+
const networkTime = requests.filter(request => request.kind === "network").reduce((total, request) => total + durationOf(request), 0);
|
|
55
|
+
const slowestRequest = top(requests, 1)[0] ?? null;
|
|
56
|
+
return {
|
|
57
|
+
id: interaction.id,
|
|
58
|
+
label: interaction.label,
|
|
59
|
+
target: interaction.target ?? interaction.detail?.target ?? null,
|
|
60
|
+
startTime: round(start),
|
|
61
|
+
durationMs: round(durationOf(interaction)),
|
|
62
|
+
requestCount: requests.length,
|
|
63
|
+
renderIssueCount: render.length,
|
|
64
|
+
insightCount: insights.length,
|
|
65
|
+
nativeApiTimeMs: round(nativeApiTime),
|
|
66
|
+
networkTimeMs: round(networkTime),
|
|
67
|
+
slowestRequest: slowestRequest ? {
|
|
68
|
+
id: slowestRequest.id,
|
|
69
|
+
kind: slowestRequest.kind,
|
|
70
|
+
label: slowestRequest.label,
|
|
71
|
+
durationMs: round(durationOf(slowestRequest))
|
|
72
|
+
} : null,
|
|
73
|
+
severity: durationOf(interaction) >= SLOW_MS.interaction || render.length > 0 || insights.length > 0 ? "warning" : "ok"
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function buildFindings(data, interactionSummaries) {
|
|
77
|
+
const findings = [];
|
|
78
|
+
const slowInteractions = interactionSummaries.filter(interaction => interaction.durationMs >= SLOW_MS.interaction);
|
|
79
|
+
const slowRequests = data.requests.filter(request => durationOf(request) >= SLOW_MS.request);
|
|
80
|
+
const slowNativeApi = data.requests.filter(request => request.kind === "native-api" && durationOf(request) >= SLOW_MS.nativeApi);
|
|
81
|
+
const renderIssues = data.metrics.filter(metric => ["fps-drop", "long-task", "loaf", "layout-shift"].includes(metric.kind));
|
|
82
|
+
const cacheMisses = data.requests.filter(request => request.cacheStatus === "miss");
|
|
83
|
+
if (slowInteractions.length > 0) {
|
|
84
|
+
findings.push({
|
|
85
|
+
severity: "warning",
|
|
86
|
+
title: `${slowInteractions.length} slow interaction${slowInteractions.length === 1 ? "" : "s"}`,
|
|
87
|
+
detail: `${slowInteractions[0].label} took ${slowInteractions[0].durationMs}ms`
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
if (slowRequests.length > 0) {
|
|
91
|
+
const slowest = top(slowRequests, 1)[0];
|
|
92
|
+
findings.push({
|
|
93
|
+
severity: "warning",
|
|
94
|
+
title: `${slowRequests.length} slow request${slowRequests.length === 1 ? "" : "s"}`,
|
|
95
|
+
detail: `${slowest.label} took ${round(durationOf(slowest))}ms`
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
if (slowNativeApi.length > 0) {
|
|
99
|
+
const slowest = top(slowNativeApi, 1)[0];
|
|
100
|
+
findings.push({
|
|
101
|
+
severity: "warning",
|
|
102
|
+
title: `${slowNativeApi.length} slow native call${slowNativeApi.length === 1 ? "" : "s"}`,
|
|
103
|
+
detail: `${slowest.label} took ${round(durationOf(slowest))}ms`
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
if (renderIssues.length > 0) {
|
|
107
|
+
findings.push({
|
|
108
|
+
severity: "warning",
|
|
109
|
+
title: `${renderIssues.length} render issue${renderIssues.length === 1 ? "" : "s"}`,
|
|
110
|
+
detail: renderIssues[0].label
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
if (cacheMisses.length > 0) {
|
|
114
|
+
findings.push({
|
|
115
|
+
severity: "info",
|
|
116
|
+
title: `${cacheMisses.length} cache miss${cacheMisses.length === 1 ? "" : "es"}`,
|
|
117
|
+
detail: cacheMisses[0].label
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
return findings;
|
|
121
|
+
}
|
|
122
|
+
function buildSummary(data) {
|
|
123
|
+
const sessions = data.sessions ?? [];
|
|
124
|
+
const requests = data.requests ?? [];
|
|
125
|
+
const metrics = data.metrics ?? [];
|
|
126
|
+
const events = data.events ?? [];
|
|
127
|
+
const insights = data.insights ?? [];
|
|
128
|
+
const interactions = sessions.filter(session => session.kind === "interaction");
|
|
129
|
+
const renderIssues = metrics.filter(metric => ["fps-drop", "long-task", "loaf", "layout-shift"].includes(metric.kind));
|
|
130
|
+
const memory = metrics.find(metric => metric.kind === "memory-summary") ?? null;
|
|
131
|
+
const interactionSummaries = top(interactions, 10).map(interaction => summarizeInteraction(interaction, data));
|
|
132
|
+
return {
|
|
133
|
+
generatedAt: new Date().toISOString(),
|
|
134
|
+
counts: {
|
|
135
|
+
events: events.length,
|
|
136
|
+
sessions: sessions.length,
|
|
137
|
+
requests: requests.length,
|
|
138
|
+
metrics: metrics.length,
|
|
139
|
+
insights: insights.length
|
|
140
|
+
},
|
|
141
|
+
byKind: {
|
|
142
|
+
sessions: countBy(sessions, "kind"),
|
|
143
|
+
requests: countBy(requests, "kind"),
|
|
144
|
+
metrics: countBy(metrics, "kind")
|
|
145
|
+
},
|
|
146
|
+
slowest: {
|
|
147
|
+
interactions: top(interactions).map(record => ({
|
|
148
|
+
id: record.id,
|
|
149
|
+
label: record.label,
|
|
150
|
+
durationMs: round(durationOf(record))
|
|
151
|
+
})),
|
|
152
|
+
requests: top(requests).map(record => ({
|
|
153
|
+
id: record.id,
|
|
154
|
+
kind: record.kind,
|
|
155
|
+
label: record.label,
|
|
156
|
+
durationMs: round(durationOf(record))
|
|
157
|
+
})),
|
|
158
|
+
render: top(renderIssues).map(record => ({
|
|
159
|
+
id: record.id,
|
|
160
|
+
kind: record.kind,
|
|
161
|
+
label: record.label,
|
|
162
|
+
durationMs: round(durationOf(record))
|
|
163
|
+
}))
|
|
164
|
+
},
|
|
165
|
+
interactions: interactionSummaries,
|
|
166
|
+
memory: memory ? {
|
|
167
|
+
label: memory.label,
|
|
168
|
+
latest: memory.detail?.latest ?? {},
|
|
169
|
+
peak: memory.detail?.peak ?? {},
|
|
170
|
+
sampleCount: memory.detail?.sampleCount ?? 0
|
|
171
|
+
} : null,
|
|
172
|
+
findings: buildFindings(data, interactionSummaries)
|
|
173
|
+
};
|
|
174
|
+
}
|
|
@@ -1 +1,240 @@
|
|
|
1
|
-
"use strict";
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.buildChromeTrace = buildChromeTrace;
|
|
7
|
+
exports.downloadChromeTrace = downloadChromeTrace;
|
|
8
|
+
const THREADS = {
|
|
9
|
+
navigation: 1,
|
|
10
|
+
input: 2,
|
|
11
|
+
render: 3,
|
|
12
|
+
memory: 4,
|
|
13
|
+
network: 5,
|
|
14
|
+
nativeApi: 6,
|
|
15
|
+
insights: 7,
|
|
16
|
+
events: 8
|
|
17
|
+
};
|
|
18
|
+
const THREAD_NAMES = {
|
|
19
|
+
[THREADS.navigation]: "Navigation",
|
|
20
|
+
[THREADS.input]: "Input",
|
|
21
|
+
[THREADS.render]: "Render",
|
|
22
|
+
[THREADS.memory]: "Memory",
|
|
23
|
+
[THREADS.network]: "Network / Cache",
|
|
24
|
+
[THREADS.nativeApi]: "Native API execution",
|
|
25
|
+
[THREADS.insights]: "Insights",
|
|
26
|
+
[THREADS.events]: "Events"
|
|
27
|
+
};
|
|
28
|
+
function us(ms) {
|
|
29
|
+
return Math.max(0, Math.round((Number(ms) || 0) * 1000));
|
|
30
|
+
}
|
|
31
|
+
function durationMs(record) {
|
|
32
|
+
if (Number.isFinite(record.durationMs)) return Math.max(0.001, record.durationMs);
|
|
33
|
+
if (Number.isFinite(record.startTime) && Number.isFinite(record.endTime)) {
|
|
34
|
+
return Math.max(0.001, record.endTime - record.startTime);
|
|
35
|
+
}
|
|
36
|
+
return 0.001;
|
|
37
|
+
}
|
|
38
|
+
function label(record, fallback) {
|
|
39
|
+
return record.label ?? record.title ?? record.name ?? record.type ?? record.kind ?? fallback;
|
|
40
|
+
}
|
|
41
|
+
function detail(record) {
|
|
42
|
+
const source = record.detail ?? record;
|
|
43
|
+
const relatedTap = record.interactionId ?? record.sessionId ?? source.interactionId ?? source.sessionId;
|
|
44
|
+
return relatedTap ? {
|
|
45
|
+
relatedTap,
|
|
46
|
+
...source
|
|
47
|
+
} : source;
|
|
48
|
+
}
|
|
49
|
+
function displayLabel(record, fallback) {
|
|
50
|
+
const base = label(record, fallback);
|
|
51
|
+
const source = record.detail ?? record;
|
|
52
|
+
const relatedTap = record.interactionId ?? record.sessionId ?? source.interactionId ?? source.sessionId;
|
|
53
|
+
if (!relatedTap) return base;
|
|
54
|
+
if (record.kind === "interaction") return `${relatedTap}: ${record.target ?? source.target ?? base}`;
|
|
55
|
+
return base.startsWith(`[${relatedTap}]`) ? base : `[${relatedTap}] ${base}`;
|
|
56
|
+
}
|
|
57
|
+
function sessionThread(kind) {
|
|
58
|
+
if (kind === "page-load" || kind === "route") return THREADS.navigation;
|
|
59
|
+
if (kind === "scroll" || kind === "keyboard" || kind === "interaction") return THREADS.input;
|
|
60
|
+
if (kind === "hw-accel" || kind === "hook") return THREADS.render;
|
|
61
|
+
return THREADS.events;
|
|
62
|
+
}
|
|
63
|
+
function requestThread(kind) {
|
|
64
|
+
if (kind === "native-api") return THREADS.nativeApi;
|
|
65
|
+
return THREADS.network;
|
|
66
|
+
}
|
|
67
|
+
function metricThread(kind) {
|
|
68
|
+
if (kind === "memory" || kind === "memory-summary") return THREADS.memory;
|
|
69
|
+
if (kind === "startup" || kind === "paint") return THREADS.navigation;
|
|
70
|
+
return THREADS.render;
|
|
71
|
+
}
|
|
72
|
+
function addMetadata(events, pid) {
|
|
73
|
+
events.push({
|
|
74
|
+
ph: "M",
|
|
75
|
+
pid,
|
|
76
|
+
tid: 0,
|
|
77
|
+
name: "process_name",
|
|
78
|
+
args: {
|
|
79
|
+
name: "Catalyst"
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
for (const [tid, name] of Object.entries(THREAD_NAMES)) {
|
|
83
|
+
events.push({
|
|
84
|
+
ph: "M",
|
|
85
|
+
pid,
|
|
86
|
+
tid: Number(tid),
|
|
87
|
+
name: "thread_name",
|
|
88
|
+
args: {
|
|
89
|
+
name
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function addDuration(events, record, tid, category, fallbackName) {
|
|
95
|
+
const startTime = record.startTime ?? record.time ?? 0;
|
|
96
|
+
events.push({
|
|
97
|
+
name: displayLabel(record, fallbackName),
|
|
98
|
+
cat: category,
|
|
99
|
+
ph: "X",
|
|
100
|
+
ts: us(startTime),
|
|
101
|
+
dur: us(durationMs(record)),
|
|
102
|
+
pid: 1,
|
|
103
|
+
tid,
|
|
104
|
+
args: detail(record)
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
function addInstant(events, record, tid, category, fallbackName) {
|
|
108
|
+
events.push({
|
|
109
|
+
name: displayLabel(record, fallbackName),
|
|
110
|
+
cat: category,
|
|
111
|
+
ph: "i",
|
|
112
|
+
s: "t",
|
|
113
|
+
ts: us(record.startTime ?? record.time ?? 0),
|
|
114
|
+
pid: 1,
|
|
115
|
+
tid,
|
|
116
|
+
args: detail(record)
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
function addTapMarker(events, session) {
|
|
120
|
+
const source = session.detail ?? session;
|
|
121
|
+
const tap = session.interactionId ?? session.sessionId ?? source.interactionId ?? source.sessionId;
|
|
122
|
+
if (!tap) return;
|
|
123
|
+
events.push({
|
|
124
|
+
name: `${tap}: ${session.target ?? source.target ?? "interaction"}`,
|
|
125
|
+
cat: "Catalyst Interaction",
|
|
126
|
+
ph: "i",
|
|
127
|
+
s: "g",
|
|
128
|
+
ts: us(session.startTime ?? 0),
|
|
129
|
+
pid: 1,
|
|
130
|
+
tid: THREADS.input,
|
|
131
|
+
args: {
|
|
132
|
+
relatedTap: tap,
|
|
133
|
+
target: session.target ?? source.target ?? "unknown"
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
function addCounter(events, record) {
|
|
138
|
+
const args = {};
|
|
139
|
+
const source = record.detail ?? record;
|
|
140
|
+
for (const key of ["jvmMb", "webviewMb", "nativeMb", "otherMb", "totalMb", "peakMb"]) {
|
|
141
|
+
if (Number.isFinite(source[key])) args[key] = source[key];
|
|
142
|
+
}
|
|
143
|
+
if (Object.keys(args).length === 0 && Number.isFinite(record.value)) {
|
|
144
|
+
args.value = record.value;
|
|
145
|
+
}
|
|
146
|
+
events.push({
|
|
147
|
+
name: label(record, "Memory"),
|
|
148
|
+
cat: "Catalyst Memory",
|
|
149
|
+
ph: "C",
|
|
150
|
+
ts: us(record.startTime ?? record.time ?? 0),
|
|
151
|
+
pid: 1,
|
|
152
|
+
tid: THREADS.memory,
|
|
153
|
+
args
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
function buildChromeTrace(data) {
|
|
157
|
+
const traceEvents = [];
|
|
158
|
+
addMetadata(traceEvents, 1);
|
|
159
|
+
for (const session of data.sessions ?? []) {
|
|
160
|
+
addDuration(traceEvents, session, sessionThread(session.kind), `Catalyst ${session.kind}`, "Session");
|
|
161
|
+
if (session.kind === "interaction") addTapMarker(traceEvents, session);
|
|
162
|
+
}
|
|
163
|
+
for (const request of data.requests ?? []) {
|
|
164
|
+
addDuration(traceEvents, request, requestThread(request.kind), `Catalyst ${request.kind}`, "Request");
|
|
165
|
+
}
|
|
166
|
+
const hasMemorySamples = (data.metrics ?? []).some(metric => metric.kind === "memory");
|
|
167
|
+
for (const metric of data.metrics ?? []) {
|
|
168
|
+
if (metric.kind === "memory-summary" && hasMemorySamples) continue;
|
|
169
|
+
if (metric.kind === "memory" || metric.kind === "memory-summary") {
|
|
170
|
+
addCounter(traceEvents, metric);
|
|
171
|
+
} else {
|
|
172
|
+
addDuration(traceEvents, metric, metricThread(metric.kind), `Catalyst ${metric.kind}`, "Metric");
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
for (const event of data.events ?? []) {
|
|
176
|
+
addInstant(traceEvents, event, THREADS.events, `Catalyst ${event.kind ?? "event"}`, "Event");
|
|
177
|
+
}
|
|
178
|
+
for (const insight of data.insights ?? []) {
|
|
179
|
+
addInstant(traceEvents, insight, THREADS.insights, "Catalyst Insights", "Insight");
|
|
180
|
+
}
|
|
181
|
+
return {
|
|
182
|
+
traceEvents: traceEvents.sort((a, b) => (a.ts ?? 0) - (b.ts ?? 0)),
|
|
183
|
+
metadata: {
|
|
184
|
+
source: "CatalystPerf",
|
|
185
|
+
createdAt: new Date().toISOString(),
|
|
186
|
+
storeCreatedAt: data.createdAt ?? null,
|
|
187
|
+
version: data.version ?? 1
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
function downloadChromeTrace(data, filename) {
|
|
192
|
+
const trace = buildChromeTrace(data);
|
|
193
|
+
const traceJson = JSON.stringify(trace);
|
|
194
|
+
const resolvedFilename = filename ?? `catalyst-trace-${new Date().toISOString().replace(/[:.]/g, "-")}.json`;
|
|
195
|
+
const eventCount = trace.traceEvents.length;
|
|
196
|
+
const isIOSNative = typeof window !== "undefined" && !!window.webkit?.messageHandlers?.NativeBridge;
|
|
197
|
+
const isAndroidNative = typeof window !== "undefined" && !!window.NativeBridge;
|
|
198
|
+
if (typeof console !== "undefined") {
|
|
199
|
+
console.info(`[CatalystPerf] Trace ready: ${resolvedFilename} (${eventCount} events)`);
|
|
200
|
+
}
|
|
201
|
+
if (isIOSNative) {
|
|
202
|
+
window.webkit.messageHandlers.NativeBridge.postMessage({
|
|
203
|
+
command: "exportCatalystTrace",
|
|
204
|
+
data: {
|
|
205
|
+
filename: resolvedFilename,
|
|
206
|
+
trace: traceJson
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
if (typeof console !== "undefined") {
|
|
210
|
+
console.info(`[CatalystPerf] Sent ${resolvedFilename} to the iOS share sheet.`);
|
|
211
|
+
}
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
if (isAndroidNative) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
if (typeof document === "undefined" || typeof Blob === "undefined" || typeof URL === "undefined") {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
const link = document.createElement("a");
|
|
221
|
+
if (!("download" in link)) {
|
|
222
|
+
if (typeof console !== "undefined") {
|
|
223
|
+
console.warn("[CatalystPerf] Browser does not support blob downloads. Use CatalystPerf.trace() instead.");
|
|
224
|
+
}
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
const blob = new Blob([traceJson], {
|
|
228
|
+
type: "application/json"
|
|
229
|
+
});
|
|
230
|
+
const url = URL.createObjectURL(blob);
|
|
231
|
+
link.href = url;
|
|
232
|
+
link.download = resolvedFilename;
|
|
233
|
+
document.body.appendChild(link);
|
|
234
|
+
link.click();
|
|
235
|
+
link.remove();
|
|
236
|
+
setTimeout(() => URL.revokeObjectURL(url), 30000);
|
|
237
|
+
if (typeof console !== "undefined") {
|
|
238
|
+
console.info(`[CatalystPerf] Download started. Check your browser Downloads for ${resolvedFilename}.`);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
@@ -1 +1,146 @@
|
|
|
1
|
-
"use strict";
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.buildWaterfall = buildWaterfall;
|
|
7
|
+
exports.listWaterfalls = listWaterfalls;
|
|
8
|
+
const REQUEST_LANES = new Set(["network", "cache", "native-api"]);
|
|
9
|
+
function durationOf(row) {
|
|
10
|
+
if (Number.isFinite(row.durationMs)) return row.durationMs;
|
|
11
|
+
if (Number.isFinite(row.startTime) && Number.isFinite(row.endTime)) {
|
|
12
|
+
return Math.max(0, Math.round(row.endTime - row.startTime));
|
|
13
|
+
}
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
function toRow(record, lane, group = null) {
|
|
17
|
+
const startTime = record.startTime ?? record.time ?? 0;
|
|
18
|
+
const endTime = record.endTime ?? startTime;
|
|
19
|
+
return {
|
|
20
|
+
id: record.id,
|
|
21
|
+
lane,
|
|
22
|
+
group,
|
|
23
|
+
kind: record.kind ?? record.type ?? lane,
|
|
24
|
+
label: record.label ?? record.name ?? record.type ?? record.kind ?? lane,
|
|
25
|
+
startTime,
|
|
26
|
+
endTime,
|
|
27
|
+
durationMs: durationOf({
|
|
28
|
+
...record,
|
|
29
|
+
startTime,
|
|
30
|
+
endTime
|
|
31
|
+
}),
|
|
32
|
+
severity: record.severity ?? null,
|
|
33
|
+
detail: record.detail ?? record
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function byStartTime(a, b) {
|
|
37
|
+
return (a.startTime ?? 0) - (b.startTime ?? 0);
|
|
38
|
+
}
|
|
39
|
+
function overlaps(row, start, end) {
|
|
40
|
+
if (!Number.isFinite(start) || !Number.isFinite(end)) return false;
|
|
41
|
+
const rowStart = row.startTime ?? 0;
|
|
42
|
+
const rowEnd = row.endTime ?? rowStart;
|
|
43
|
+
return rowStart < end && rowEnd >= start;
|
|
44
|
+
}
|
|
45
|
+
function isStandaloneEvent(event) {
|
|
46
|
+
return event.kind === "startup" || event.type === "navigation-back" || event.type === "page-load-error";
|
|
47
|
+
}
|
|
48
|
+
function isTimelineMetric(metric) {
|
|
49
|
+
return metric.kind !== "memory" && metric.kind !== "memory-summary";
|
|
50
|
+
}
|
|
51
|
+
function rowsForWindow(data, start, end, group) {
|
|
52
|
+
return [...data.sessions.map(session => toRow(session, session.kind, group)), ...data.requests.map(request => toRow(request, request.kind, group)), ...data.metrics.filter(isTimelineMetric).map(metric => toRow(metric, metric.kind, group)), ...data.events.filter(isStandaloneEvent).map(event => toRow(event, event.kind ?? "event", group)), ...data.insights.map(insight => toRow(insight, "insight", group))].filter(row => overlaps(row, start, end)).sort(byStartTime);
|
|
53
|
+
}
|
|
54
|
+
function buildPageLoadWaterfall(data) {
|
|
55
|
+
const pageLoads = data.sessions.filter(session => session.kind === "page-load");
|
|
56
|
+
if (pageLoads.length === 0) {
|
|
57
|
+
return [...data.events.filter(event => event.kind === "startup" || event.type?.startsWith("boot-")).map(event => toRow(event, "startup", "page-load")), ...data.metrics.filter(metric => metric.kind === "paint" || metric.kind === "startup").map(metric => toRow(metric, metric.kind, "page-load"))].sort(byStartTime);
|
|
58
|
+
}
|
|
59
|
+
return pageLoads.flatMap((pageLoad, index) => {
|
|
60
|
+
const group = pageLoad.label;
|
|
61
|
+
const startupRows = index === 0 ? data.events.filter(event => event.kind === "startup" && (event.startTime ?? 0) <= pageLoad.endTime).map(event => toRow(event, "startup", group)) : [];
|
|
62
|
+
return [...startupRows, toRow(pageLoad, "page-load", group), ...rowsForWindow(data, pageLoad.startTime, pageLoad.endTime, group).filter(row => row.id !== pageLoad.id)];
|
|
63
|
+
}).sort(byStartTime);
|
|
64
|
+
}
|
|
65
|
+
function buildRouteWaterfall(data) {
|
|
66
|
+
const routes = data.sessions.filter(session => session.kind === "route");
|
|
67
|
+
return routes.flatMap(route => [toRow(route, "route", route.label), ...rowsForWindow(data, route.startTime, route.endTime + 1000, route.label).filter(row => row.id !== route.id)]).sort(byStartTime);
|
|
68
|
+
}
|
|
69
|
+
function buildRequestWaterfall(data) {
|
|
70
|
+
return data.requests.filter(request => REQUEST_LANES.has(request.kind)).map(request => toRow(request, request.kind, request.sessionId ?? request.navigationId ?? null)).sort(byStartTime);
|
|
71
|
+
}
|
|
72
|
+
function buildNativeApiWaterfall(data) {
|
|
73
|
+
return data.requests.filter(request => request.kind === "native-api").map(request => toRow(request, request.kind, request.sessionId ?? null)).sort(byStartTime);
|
|
74
|
+
}
|
|
75
|
+
function buildInteractionWaterfall(data) {
|
|
76
|
+
const interactions = data.sessions.filter(session => session.kind === "interaction");
|
|
77
|
+
return interactions.flatMap(interaction => {
|
|
78
|
+
const group = interaction.label;
|
|
79
|
+
const matchingSession = row => {
|
|
80
|
+
const detail = row.detail ?? {};
|
|
81
|
+
return detail.sessionId === interaction.sessionId || detail.interactionId === interaction.sessionId || overlaps(row, interaction.startTime, interaction.endTime);
|
|
82
|
+
};
|
|
83
|
+
return [toRow(interaction, "interaction", group), ...[...data.requests.map(request => toRow(request, request.kind, group)), ...data.metrics.filter(isTimelineMetric).map(metric => toRow(metric, metric.kind, group)), ...data.insights.map(insight => toRow(insight, "insight", group))].filter(row => row.id !== interaction.id && matchingSession(row))];
|
|
84
|
+
}).sort(byStartTime);
|
|
85
|
+
}
|
|
86
|
+
function buildRenderJankWaterfall(data) {
|
|
87
|
+
const renderKinds = new Set(["fps-drop", "long-task", "loaf", "layout-shift", "hw-accel"]);
|
|
88
|
+
return [...data.metrics.filter(metric => renderKinds.has(metric.kind)).map(metric => toRow(metric, "render", metric.context ?? null)), ...data.sessions.filter(session => session.kind === "hw-accel").map(session => toRow(session, "render", session.detail?.trigger ?? null))].sort(byStartTime);
|
|
89
|
+
}
|
|
90
|
+
function buildScrollWaterfall(data) {
|
|
91
|
+
const scrolls = data.sessions.filter(session => session.kind === "scroll");
|
|
92
|
+
return scrolls.flatMap(scroll => [toRow(scroll, "scroll", scroll.label), ...rowsForWindow(data, scroll.startTime, scroll.endTime, scroll.label).filter(row => row.id !== scroll.id && ["fps-drop", "long-task", "loaf", "layout-shift", "insight"].includes(row.kind))]).sort(byStartTime);
|
|
93
|
+
}
|
|
94
|
+
function buildKeyboardWaterfall(data) {
|
|
95
|
+
const keyboards = data.sessions.filter(session => session.kind === "keyboard");
|
|
96
|
+
return keyboards.flatMap(keyboard => [toRow(keyboard, "keyboard", keyboard.label), ...rowsForWindow(data, keyboard.startTime, keyboard.endTime, keyboard.label).filter(row => row.id !== keyboard.id && ["viewport-resize", "scroll", "layout-shift", "insight"].includes(row.kind))]).sort(byStartTime);
|
|
97
|
+
}
|
|
98
|
+
function buildCacheWaterfall(data) {
|
|
99
|
+
return [...data.requests.filter(request => request.kind === "cache").map(request => toRow(request, "cache", request.cacheStatus ?? null)), ...data.metrics.filter(metric => metric.kind === "cache-summary").map(metric => toRow(metric, "cache-summary"))].sort(byStartTime);
|
|
100
|
+
}
|
|
101
|
+
function buildMemoryTimeline(data) {
|
|
102
|
+
return data.metrics.filter(metric => metric.kind === "memory" || metric.kind === "memory-summary").map(metric => toRow(metric, "memory")).sort(byStartTime);
|
|
103
|
+
}
|
|
104
|
+
function buildAllWaterfall(data) {
|
|
105
|
+
return [...data.sessions.map(session => toRow(session, session.kind)), ...data.requests.map(request => toRow(request, request.kind)), ...data.metrics.filter(isTimelineMetric).map(metric => toRow(metric, metric.kind)), ...data.events.filter(isStandaloneEvent).map(event => toRow(event, event.kind ?? "event")), ...data.insights.map(insight => toRow(insight, "insight"))].sort(byStartTime);
|
|
106
|
+
}
|
|
107
|
+
function buildWaterfall(data, type = "all") {
|
|
108
|
+
switch (type) {
|
|
109
|
+
case "page-load":
|
|
110
|
+
case "page":
|
|
111
|
+
return buildPageLoadWaterfall(data);
|
|
112
|
+
case "route":
|
|
113
|
+
case "navigation":
|
|
114
|
+
return buildRouteWaterfall(data);
|
|
115
|
+
case "requests":
|
|
116
|
+
case "request":
|
|
117
|
+
return buildRequestWaterfall(data);
|
|
118
|
+
case "native-bridge":
|
|
119
|
+
case "bridge":
|
|
120
|
+
case "api":
|
|
121
|
+
case "native-api":
|
|
122
|
+
return buildNativeApiWaterfall(data);
|
|
123
|
+
case "interactions":
|
|
124
|
+
case "interaction":
|
|
125
|
+
return buildInteractionWaterfall(data);
|
|
126
|
+
case "render":
|
|
127
|
+
case "jank":
|
|
128
|
+
case "render-jank":
|
|
129
|
+
return buildRenderJankWaterfall(data);
|
|
130
|
+
case "scroll":
|
|
131
|
+
return buildScrollWaterfall(data);
|
|
132
|
+
case "keyboard":
|
|
133
|
+
case "input":
|
|
134
|
+
return buildKeyboardWaterfall(data);
|
|
135
|
+
case "cache":
|
|
136
|
+
return buildCacheWaterfall(data);
|
|
137
|
+
case "memory":
|
|
138
|
+
return buildMemoryTimeline(data);
|
|
139
|
+
case "all":
|
|
140
|
+
default:
|
|
141
|
+
return buildAllWaterfall(data);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
function listWaterfalls() {
|
|
145
|
+
return ["page-load", "navigation", "requests", "native-api", "interactions", "render-jank", "scroll", "keyboard", "cache", "memory", "all"];
|
|
146
|
+
}
|