chore(trace viewer): decouple test server from web server (#33542)
This commit is contained in:
parent
25c039401d
commit
445ff73c6e
|
|
@ -125,6 +125,8 @@ export async function installRootRedirect(server: HttpServer, traceUrls: string[
|
||||||
for (const reporter of options.reporter || [])
|
for (const reporter of options.reporter || [])
|
||||||
params.append('reporter', reporter);
|
params.append('reporter', reporter);
|
||||||
|
|
||||||
|
params.set('server', server.urlPrefix('precise'));
|
||||||
|
|
||||||
const urlPath = `./trace/${options.webApp || 'index.html'}?${params.toString()}`;
|
const urlPath = `./trace/${options.webApp || 'index.html'}?${params.toString()}`;
|
||||||
server.routePath('/', (_, response) => {
|
server.routePath('/', (_, response) => {
|
||||||
response.statusCode = 302;
|
response.statusCode = 302;
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ import { splitProgress } from './progress';
|
||||||
import { unwrapPopoutUrl } from './snapshotRenderer';
|
import { unwrapPopoutUrl } from './snapshotRenderer';
|
||||||
import { SnapshotServer } from './snapshotServer';
|
import { SnapshotServer } from './snapshotServer';
|
||||||
import { TraceModel } from './traceModel';
|
import { TraceModel } from './traceModel';
|
||||||
import { FetchTraceModelBackend, ZipTraceModelBackend } from './traceModelBackends';
|
import { FetchTraceModelBackend, TraceViewerServer, ZipTraceModelBackend } from './traceModelBackends';
|
||||||
import { TraceVersionError } from './traceModernizer';
|
import { TraceVersionError } from './traceModernizer';
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
|
@ -36,13 +36,21 @@ const scopePath = new URL(self.registration.scope).pathname;
|
||||||
|
|
||||||
const loadedTraces = new Map<string, { traceModel: TraceModel, snapshotServer: SnapshotServer }>();
|
const loadedTraces = new Map<string, { traceModel: TraceModel, snapshotServer: SnapshotServer }>();
|
||||||
|
|
||||||
const clientIdToTraceUrls = new Map<string, { limit: number | undefined, traceUrls: Set<string> }>();
|
const clientIdToTraceUrls = new Map<string, { limit: number | undefined, traceUrls: Set<string>, traceViewerServer: TraceViewerServer }>();
|
||||||
|
|
||||||
async function loadTrace(traceUrl: string, traceFileName: string | null, clientId: string, limit: number | undefined, progress: (done: number, total: number) => undefined): Promise<TraceModel> {
|
async function loadTrace(traceUrl: string, traceFileName: string | null, client: any | undefined, limit: number | undefined, progress: (done: number, total: number) => undefined): Promise<TraceModel> {
|
||||||
await gc();
|
await gc();
|
||||||
|
const clientId = client?.id ?? '';
|
||||||
let data = clientIdToTraceUrls.get(clientId);
|
let data = clientIdToTraceUrls.get(clientId);
|
||||||
if (!data) {
|
if (!data) {
|
||||||
data = { limit, traceUrls: new Set() };
|
let traceViewerServerBaseUrl = self.registration.scope;
|
||||||
|
if (client?.url) {
|
||||||
|
const clientUrl = new URL(client.url);
|
||||||
|
if (clientUrl.searchParams.has('server'))
|
||||||
|
traceViewerServerBaseUrl = clientUrl.searchParams.get('server')!;
|
||||||
|
}
|
||||||
|
|
||||||
|
data = { limit, traceUrls: new Set(), traceViewerServer: new TraceViewerServer(traceViewerServerBaseUrl) };
|
||||||
clientIdToTraceUrls.set(clientId, data);
|
clientIdToTraceUrls.set(clientId, data);
|
||||||
}
|
}
|
||||||
data.traceUrls.add(traceUrl);
|
data.traceUrls.add(traceUrl);
|
||||||
|
|
@ -51,7 +59,7 @@ async function loadTrace(traceUrl: string, traceFileName: string | null, clientI
|
||||||
try {
|
try {
|
||||||
// Allow 10% to hop from sw to page.
|
// Allow 10% to hop from sw to page.
|
||||||
const [fetchProgress, unzipProgress] = splitProgress(progress, [0.5, 0.4, 0.1]);
|
const [fetchProgress, unzipProgress] = splitProgress(progress, [0.5, 0.4, 0.1]);
|
||||||
const backend = traceUrl.endsWith('json') ? new FetchTraceModelBackend(traceUrl) : new ZipTraceModelBackend(traceUrl, fetchProgress);
|
const backend = traceUrl.endsWith('json') ? new FetchTraceModelBackend(traceUrl, data.traceViewerServer) : new ZipTraceModelBackend(traceUrl, data.traceViewerServer, fetchProgress);
|
||||||
await traceModel.load(backend, unzipProgress);
|
await traceModel.load(backend, unzipProgress);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
|
|
@ -98,7 +106,7 @@ async function doFetch(event: FetchEvent): Promise<Response> {
|
||||||
if (relativePath === '/contexts') {
|
if (relativePath === '/contexts') {
|
||||||
try {
|
try {
|
||||||
const limit = url.searchParams.has('limit') ? +url.searchParams.get('limit')! : undefined;
|
const limit = url.searchParams.has('limit') ? +url.searchParams.get('limit')! : undefined;
|
||||||
const traceModel = await loadTrace(traceUrl!, url.searchParams.get('traceFileName'), event.clientId, limit, (done: number, total: number) => {
|
const traceModel = await loadTrace(traceUrl!, url.searchParams.get('traceFileName'), client, limit, (done: number, total: number) => {
|
||||||
client.postMessage({ method: 'progress', params: { done, total } });
|
client.postMessage({ method: 'progress', params: { done, total } });
|
||||||
});
|
});
|
||||||
return new Response(JSON.stringify(traceModel!.contextEntries), {
|
return new Response(JSON.stringify(traceModel!.contextEntries), {
|
||||||
|
|
@ -148,7 +156,18 @@ async function doFetch(event: FetchEvent): Promise<Response> {
|
||||||
return new Response(null, { status: 404 });
|
return new Response(null, { status: 404 });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback to network.
|
if (relativePath.startsWith('/file/')) {
|
||||||
|
const path = url.searchParams.get('path')!;
|
||||||
|
const traceViewerServer = clientIdToTraceUrls.get(event.clientId ?? '')?.traceViewerServer;
|
||||||
|
if (!traceViewerServer)
|
||||||
|
throw new Error('client is not initialized');
|
||||||
|
const response = await traceViewerServer.readFile(path);
|
||||||
|
if (!response)
|
||||||
|
return new Response(null, { status: 404 });
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback for static assets.
|
||||||
return fetch(event.request);
|
return fetch(event.request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,11 +28,11 @@ export class ZipTraceModelBackend implements TraceModelBackend {
|
||||||
private _entriesPromise: Promise<Map<string, zip.Entry>>;
|
private _entriesPromise: Promise<Map<string, zip.Entry>>;
|
||||||
private _traceURL: string;
|
private _traceURL: string;
|
||||||
|
|
||||||
constructor(traceURL: string, progress: Progress) {
|
constructor(traceURL: string, server: TraceViewerServer, progress: Progress) {
|
||||||
this._traceURL = traceURL;
|
this._traceURL = traceURL;
|
||||||
zipjs.configure({ baseURL: self.location.href } as any);
|
zipjs.configure({ baseURL: self.location.href } as any);
|
||||||
this._zipReader = new zipjs.ZipReader(
|
this._zipReader = new zipjs.ZipReader(
|
||||||
new zipjs.HttpReader(formatUrl(traceURL), { mode: 'cors', preventHeadRequest: true } as any),
|
new zipjs.HttpReader(formatUrl(traceURL, server), { mode: 'cors', preventHeadRequest: true } as any),
|
||||||
{ useWebWorkers: false });
|
{ useWebWorkers: false });
|
||||||
this._entriesPromise = this._zipReader.getEntries({ onprogress: progress }).then(entries => {
|
this._entriesPromise = this._zipReader.getEntries({ onprogress: progress }).then(entries => {
|
||||||
const map = new Map<string, zip.Entry>();
|
const map = new Map<string, zip.Entry>();
|
||||||
|
|
@ -83,12 +83,16 @@ export class ZipTraceModelBackend implements TraceModelBackend {
|
||||||
|
|
||||||
export class FetchTraceModelBackend implements TraceModelBackend {
|
export class FetchTraceModelBackend implements TraceModelBackend {
|
||||||
private _entriesPromise: Promise<Map<string, string>>;
|
private _entriesPromise: Promise<Map<string, string>>;
|
||||||
private _traceURL: string;
|
private _path: string;
|
||||||
|
private _server: TraceViewerServer;
|
||||||
|
|
||||||
constructor(traceURL: string) {
|
constructor(path: string, server: TraceViewerServer) {
|
||||||
this._traceURL = traceURL;
|
this._path = path;
|
||||||
this._entriesPromise = fetch('/trace/file?path=' + encodeURIComponent(traceURL)).then(async response => {
|
this._server = server;
|
||||||
const json = JSON.parse(await response.text());
|
this._entriesPromise = server.readFile(path).then(async response => {
|
||||||
|
if (!response)
|
||||||
|
throw new Error('File not found');
|
||||||
|
const json = await response.json();
|
||||||
const entries = new Map<string, string>();
|
const entries = new Map<string, string>();
|
||||||
for (const entry of json.entries)
|
for (const entry of json.entries)
|
||||||
entries.set(entry.name, entry.path);
|
entries.set(entry.name, entry.path);
|
||||||
|
|
@ -101,7 +105,7 @@ export class FetchTraceModelBackend implements TraceModelBackend {
|
||||||
}
|
}
|
||||||
|
|
||||||
traceURL(): string {
|
traceURL(): string {
|
||||||
return this._traceURL;
|
return this._path;
|
||||||
}
|
}
|
||||||
|
|
||||||
async entryNames(): Promise<string[]> {
|
async entryNames(): Promise<string[]> {
|
||||||
|
|
@ -129,14 +133,31 @@ export class FetchTraceModelBackend implements TraceModelBackend {
|
||||||
const fileName = entries.get(entryName);
|
const fileName = entries.get(entryName);
|
||||||
if (!fileName)
|
if (!fileName)
|
||||||
return;
|
return;
|
||||||
return fetch('/trace/file?path=' + encodeURIComponent(fileName));
|
return this._server.readFile(fileName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatUrl(trace: string) {
|
function formatUrl(trace: string, server: TraceViewerServer) {
|
||||||
let url = trace.startsWith('http') || trace.startsWith('blob') ? trace : `file?path=${encodeURIComponent(trace)}`;
|
let url = trace.startsWith('http') || trace.startsWith('blob') ? trace : server.getFileURL(trace).toString();
|
||||||
// Dropbox does not support cors.
|
// Dropbox does not support cors.
|
||||||
if (url.startsWith('https://www.dropbox.com/'))
|
if (url.startsWith('https://www.dropbox.com/'))
|
||||||
url = 'https://dl.dropboxusercontent.com/' + url.substring('https://www.dropbox.com/'.length);
|
url = 'https://dl.dropboxusercontent.com/' + url.substring('https://www.dropbox.com/'.length);
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class TraceViewerServer {
|
||||||
|
constructor(private readonly baseUrl: string) {}
|
||||||
|
|
||||||
|
getFileURL(path: string): URL {
|
||||||
|
const url = new URL('trace/file', this.baseUrl);
|
||||||
|
url.searchParams.set('path', path);
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
async readFile(path: string): Promise<Response | undefined> {
|
||||||
|
const response = await fetch(this.getFileURL(path));
|
||||||
|
if (response.status === 404)
|
||||||
|
return;
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -328,6 +328,7 @@ export function collectSnapshots(action: ActionTraceEvent | undefined): Snapshot
|
||||||
}
|
}
|
||||||
|
|
||||||
const isUnderTest = new URLSearchParams(window.location.search).has('isUnderTest');
|
const isUnderTest = new URLSearchParams(window.location.search).has('isUnderTest');
|
||||||
|
const serverParam = new URLSearchParams(window.location.search).get('server');
|
||||||
|
|
||||||
export function extendSnapshot(snapshot: Snapshot): SnapshotUrls {
|
export function extendSnapshot(snapshot: Snapshot): SnapshotUrls {
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
|
|
@ -346,6 +347,7 @@ export function extendSnapshot(snapshot: Snapshot): SnapshotUrls {
|
||||||
|
|
||||||
const popoutParams = new URLSearchParams();
|
const popoutParams = new URLSearchParams();
|
||||||
popoutParams.set('r', snapshotUrl);
|
popoutParams.set('r', snapshotUrl);
|
||||||
|
popoutParams.set('server', serverParam ?? '');
|
||||||
popoutParams.set('trace', context(snapshot.action).traceUrl);
|
popoutParams.set('trace', context(snapshot.action).traceUrl);
|
||||||
if (snapshot.point) {
|
if (snapshot.point) {
|
||||||
popoutParams.set('pointX', String(snapshot.point.x));
|
popoutParams.set('pointX', String(snapshot.point.x));
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue