/**
 * Copyright (c) Microsoft Corporation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import { SnapshotServer } from '@isomorphic/trace/snapshotServer';
import { TraceLoader } from '@isomorphic/trace/traceLoader';
import { TraceVersionError } from '@isomorphic/trace/traceModernizer';
import { splitProgress } from './progress';
import { FetchTraceLoaderBackend, ZipTraceLoaderBackend } from './traceLoaderBackends';
self.addEventListener('install', function (event) {
    self.skipWaiting();
});
self.addEventListener('activate', function (event) {
    event.waitUntil(self.clients.claim());
});
const scopePath = new URL(self.registration.scope).pathname;
const loadedTraces = new Map();
const clientIdToTraceUrls = new Map();
const isDeployedAsHttps = self.registration.scope.startsWith('https://');
function simulateRestart() {
    loadedTraces.clear();
    clientIdToTraceUrls.clear();
}
async function loadTraceOrError(clientId, url, progress) {
    try {
        const loadedTrace = await loadTrace(clientId, url, progress);
        return { loadedTrace };
    }
    catch (error) {
        return {
            errorResponse: new Response(JSON.stringify({ error: error?.message }), {
                status: 500,
                headers: { 'Content-Type': 'application/json' }
            })
        };
    }
}
function loadTrace(clientId, url, progress) {
    const traceUri = url.searchParams.get('trace');
    if (!traceUri)
        throw new Error('trace parameter is missing');
    clientIdToTraceUrls.set(clientId, traceUri);
    const loadedTrace = loadedTraces.get(traceUri);
    if (loadedTrace)
        return loadedTrace;
    const promise = innerLoadTrace(traceUri, progress);
    loadedTraces.set(traceUri, promise);
    promise.catch(() => loadedTraces.delete(traceUri));
    return promise;
}
async function innerLoadTrace(traceUri, progress) {
    await gc();
    const traceLoader = new TraceLoader();
    try {
        // Allow 10% to hop from sw to page.
        const [fetchProgress, unzipProgress] = splitProgress(progress, [0.5, 0.4, 0.1]);
        const backend = isLiveTrace(traceUri) || traceUri.endsWith('traces.dir') ? new FetchTraceLoaderBackend(traceUri) : new ZipTraceLoaderBackend(traceUri, fetchProgress);
        await traceLoader.load(backend, undefined, unzipProgress);
    }
    catch (error) {
        // eslint-disable-next-line no-console
        console.error(error);
        if (error?.message?.includes('Cannot find .trace file') && await traceLoader.hasEntry('index.html'))
            throw new Error('Could not load trace. Did you upload a Playwright HTML report instead? Make sure to extract the archive first and then double-click the index.html file or put it on a web server.');
        if (error instanceof TraceVersionError)
            throw new Error(`Could not load trace from ${traceUri}. ${error.message}`);
        let message = `Could not load trace from ${traceUri}. Make sure a valid Playwright Trace is accessible over this url.`;
        const lnaPermission = await navigator.permissions.query({ name: 'local-network-access' }).catch(() => { });
        if (lnaPermission && lnaPermission.state !== 'granted')
            message += `\n\nIf your trace is in a local or private network, please grant permission for Local Network Access.`; // workbenchLoader.tsx opens the prompt when it sees this message.
        throw new Error(message);
    }
    const snapshotServer = new SnapshotServer(traceLoader.storage(), sha1 => traceLoader.resourceForSha1(sha1));
    return { traceLoader, snapshotServer };
}
async function doFetch(event) {
    const request = event.request;
    // In order to make Accessibility Insights for Web work.
    if (request.url.startsWith('chrome-extension://'))
        return fetch(request);
    const url = new URL(request.url);
    let relativePath;
    if (request.url.startsWith(self.registration.scope))
        relativePath = url.pathname.substring(scopePath.length - 1);
    if (relativePath === '/restartServiceWorker') {
        simulateRestart();
        return new Response(null, { status: 200 });
    }
    if (relativePath === '/ping')
        return new Response(null, { status: 200 });
    const isNavigation = !!event.resultingClientId;
    const client = event.clientId ? await self.clients.get(event.clientId) : undefined;
    if (isNavigation && !relativePath?.startsWith('/sha1/')) {
        // Navigation request. Download is a /sha1/ navigation, ignore them here.
        // Snapshot iframe navigation request.
        if (relativePath?.startsWith('/snapshot/')) {
            // It is Ok to pass noop progress as the trace is likely already loaded.
            const { errorResponse, loadedTrace } = await loadTraceOrError(event.resultingClientId, url, noopProgress);
            if (errorResponse)
                return errorResponse;
            const pageOrFrameId = relativePath.substring('/snapshot/'.length);
            const response = loadedTrace.snapshotServer.serveSnapshot(pageOrFrameId, url.searchParams, url.href);
            if (isDeployedAsHttps)
                response.headers.set('Content-Security-Policy', 'upgrade-insecure-requests');
            return response;
        }
        // Static content navigation request for trace viewer or popout.
        return fetch(event.request);
    }
    if (!relativePath) {
        // Out-of-scope sub-resource request => iframe snapshot sub-resources.
        if (!client)
            return new Response('Sub-resource without a client', { status: 500 });
        const { snapshotServer } = await loadTrace(client.id, new URL(client.url), clientProgress(client));
        if (!snapshotServer)
            return new Response(null, { status: 404 });
        // When trace viewer is deployed over https, we will force upgrade
        // insecure http sub-resources to https. Otherwise, these will fail
        // to load inside our https snapshots.
        // In this case, we also match http resources from the archive by
        // the https urls.
        const lookupUrls = [request.url];
        if (isDeployedAsHttps && request.url.startsWith('https://'))
            lookupUrls.push(request.url.replace(/^https/, 'http'));
        return snapshotServer.serveResource(lookupUrls, request.method, client.url);
    }
    // These commands all require a loaded trace.
    if (relativePath === '/contexts' || relativePath.startsWith('/snapshotInfo/') || relativePath.startsWith('/closest-screenshot/') || relativePath.startsWith('/sha1/')) {
        if (!client)
            return new Response('Sub-resource without a client', { status: 500 });
        const { errorResponse, loadedTrace } = await loadTraceOrError(client.id, url, clientProgress(client));
        if (errorResponse)
            return errorResponse;
        if (relativePath === '/contexts') {
            return new Response(JSON.stringify(loadedTrace.traceLoader.contextEntries), {
                status: 200,
                headers: { 'Content-Type': 'application/json' }
            });
        }
        if (relativePath.startsWith('/snapshotInfo/')) {
            const pageOrFrameId = relativePath.substring('/snapshotInfo/'.length);
            return loadedTrace.snapshotServer.serveSnapshotInfo(pageOrFrameId, url.searchParams);
        }
        if (relativePath.startsWith('/closest-screenshot/')) {
            const pageOrFrameId = relativePath.substring('/closest-screenshot/'.length);
            return loadedTrace.snapshotServer.serveClosestScreenshot(pageOrFrameId, url.searchParams);
        }
        if (relativePath.startsWith('/sha1/')) {
            const blob = await loadedTrace.traceLoader.resourceForSha1(relativePath.slice('/sha1/'.length));
            if (blob)
                return new Response(blob, { status: 200, headers: downloadHeaders(url.searchParams) });
            return new Response(null, { status: 404 });
        }
    }
    // Pass through to the server for file requests and static content.
    return fetch(event.request);
}
function downloadHeaders(searchParams) {
    const name = searchParams.get('dn');
    const contentType = searchParams.get('dct');
    if (!name)
        return;
    const headers = new Headers();
    headers.set('Content-Disposition', `attachment; filename="attachment"; filename*=UTF-8''${encodeURIComponent(name)}`);
    if (contentType)
        headers.set('Content-Type', contentType);
    return headers;
}
async function gc() {
    const clients = await self.clients.matchAll();
    const usedTraces = new Set();
    for (const [clientId, traceUrl] of clientIdToTraceUrls) {
        if (!clients.find(c => c.id === clientId)) {
            clientIdToTraceUrls.delete(clientId);
            continue;
        }
        usedTraces.add(traceUrl);
    }
    for (const traceUrl of loadedTraces.keys()) {
        if (!usedTraces.has(traceUrl))
            loadedTraces.delete(traceUrl);
    }
}
function clientProgress(client) {
    return (done, total) => {
        client.postMessage({ method: 'progress', params: { done, total } });
    };
}
function noopProgress(done, total) { }
function isLiveTrace(traceUri) {
    const url = new URL(traceUri, 'http://localhost');
    const path = url.searchParams.get('path');
    return !!path?.endsWith('.json');
}
self.addEventListener('fetch', function (event) {
    if (event.request.headers.get('x-pw-serviceworker') === 'skip')
        return false;
    event.respondWith(doFetch(event));
});
//# sourceMappingURL=main.js.map