2021-10-11 19:52:28 -08:00
/ * *
* 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 .
* /
2022-09-20 18:41:51 -07:00
import { MultiMap } from './multimap' ;
2023-05-05 15:12:18 -07:00
import { splitProgress } from './progress' ;
2023-01-30 19:07:52 -08:00
import { unwrapPopoutUrl } from './snapshotRenderer' ;
2021-10-12 13:42:50 -08:00
import { SnapshotServer } from './snapshotServer' ;
import { TraceModel } from './traceModel' ;
2023-05-05 15:12:18 -07:00
import { FetchTraceModelBackend , ZipTraceModelBackend } from './traceModelBackends' ;
2021-10-11 19:52:28 -08:00
// @ts-ignore
declare const self : ServiceWorkerGlobalScope ;
2021-10-13 10:07:29 -08:00
self . addEventListener ( 'install' , function ( event : any ) {
self . skipWaiting ( ) ;
} ) ;
2021-10-11 19:52:28 -08:00
self . addEventListener ( 'activate' , function ( event : any ) {
event . waitUntil ( self . clients . claim ( ) ) ;
} ) ;
2021-10-13 10:07:29 -08:00
const scopePath = new URL ( self . registration . scope ) . pathname ;
2021-10-11 19:52:28 -08:00
2022-04-02 03:20:05 +08:00
const loadedTraces = new Map < string , { traceModel : TraceModel , snapshotServer : SnapshotServer } > ( ) ;
2022-08-15 20:54:57 -07:00
const clientIdToTraceUrls = new MultiMap < string , string > ( ) ;
2021-10-13 12:31:54 -08:00
2022-09-26 20:57:05 +02:00
async function loadTrace ( traceUrl : string , traceFileName : string | null , clientId : string , progress : ( done : number , total : number ) = > void ) : Promise < TraceModel > {
clientIdToTraceUrls . set ( clientId , traceUrl ) ;
2021-10-12 13:42:50 -08:00
const traceModel = new TraceModel ( ) ;
2022-09-26 20:57:05 +02:00
try {
2023-05-05 15:12:18 -07:00
// Allow 10% to hop from sw to page.
const [ fetchProgress , unzipProgress ] = splitProgress ( progress , [ 0.5 , 0.4 , 0.1 ] ) ;
const backend = traceUrl . endsWith ( 'json' ) ? new FetchTraceModelBackend ( traceUrl ) : new ZipTraceModelBackend ( traceUrl , fetchProgress ) ;
await traceModel . load ( backend , unzipProgress ) ;
2022-09-26 20:57:05 +02:00
} catch ( error : any ) {
if ( error ? . message ? . includes ( 'Cannot find .trace file' ) && await traceModel . 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.' ) ;
2023-03-23 12:49:53 -07:00
if ( traceFileName )
2022-09-26 20:57:05 +02:00
throw new Error ( ` Could not load trace from ${ traceFileName } . Make sure to upload a valid Playwright trace. ` ) ;
2023-03-23 12:49:53 -07:00
throw new Error ( ` Could not load trace from ${ traceUrl } . Make sure a valid Playwright Trace is accessible over this url. ` ) ;
2022-09-26 20:57:05 +02:00
}
2023-03-22 09:32:21 -07:00
const snapshotServer = new SnapshotServer ( traceModel . storage ( ) , sha1 = > traceModel . resourceForSha1 ( sha1 ) ) ;
2022-09-26 20:57:05 +02:00
loadedTraces . set ( traceUrl , { traceModel , snapshotServer } ) ;
2021-10-12 13:42:50 -08:00
return traceModel ;
2021-10-11 19:52:28 -08:00
}
2021-10-12 13:42:50 -08:00
// @ts-ignore
async function doFetch ( event : FetchEvent ) : Promise < Response > {
2021-10-11 19:52:28 -08:00
const request = event . request ;
2021-10-22 15:59:17 -08:00
const client = await self . clients . get ( event . clientId ) ;
2021-10-11 19:52:28 -08:00
2023-03-01 15:32:39 -08:00
// When trace viewer is deployed over https, we will force upgrade
// insecure http subresources 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 isDeployedAsHttps = self . registration . scope . startsWith ( 'https://' ) ;
2021-10-13 10:07:29 -08:00
if ( request . url . startsWith ( self . registration . scope ) ) {
2023-01-30 19:07:52 -08:00
const url = new URL ( unwrapPopoutUrl ( request . url ) ) ;
2021-10-13 10:07:29 -08:00
const relativePath = url . pathname . substring ( scopePath . length - 1 ) ;
2021-10-22 14:14:58 -08:00
if ( relativePath === '/ping' ) {
2021-10-13 12:31:54 -08:00
await gc ( ) ;
2021-10-22 14:14:58 -08:00
return new Response ( null , { status : 200 } ) ;
}
2021-11-11 21:31:19 +01:00
const traceUrl = url . searchParams . get ( 'trace' ) ! ;
2021-11-09 15:12:37 -08:00
const { snapshotServer } = loadedTraces . get ( traceUrl ) || { } ;
2023-02-27 22:31:47 -08:00
if ( relativePath === '/contexts' ) {
2021-11-11 21:31:19 +01:00
try {
2022-09-26 20:57:05 +02:00
const traceModel = await loadTrace ( traceUrl , url . searchParams . get ( 'traceFileName' ) , event . clientId , ( done : number , total : number ) = > {
2021-11-11 21:31:19 +01:00
client . postMessage ( { method : 'progress' , params : { done , total } } ) ;
} ) ;
2023-02-27 22:31:47 -08:00
return new Response ( JSON . stringify ( traceModel ! . contextEntries ) , {
2021-11-11 21:31:19 +01:00
status : 200 ,
headers : { 'Content-Type' : 'application/json' }
} ) ;
2022-09-26 20:57:05 +02:00
} catch ( error : any ) {
return new Response ( JSON . stringify ( { error : error?.message } ) , {
2021-11-11 21:31:19 +01:00
status : 500 ,
headers : { 'Content-Type' : 'application/json' }
} ) ;
}
2021-10-12 13:42:50 -08:00
}
2021-10-13 12:31:54 -08:00
2021-11-02 16:35:23 -08:00
if ( relativePath . startsWith ( '/snapshotInfo/' ) ) {
2021-10-13 12:31:54 -08:00
if ( ! snapshotServer )
2021-10-12 13:42:50 -08:00
return new Response ( null , { status : 404 } ) ;
2021-11-02 16:35:23 -08:00
return snapshotServer . serveSnapshotInfo ( relativePath , url . searchParams ) ;
2021-10-12 13:42:50 -08:00
}
2021-10-13 12:31:54 -08:00
if ( relativePath . startsWith ( '/snapshot/' ) ) {
if ( ! snapshotServer )
return new Response ( null , { status : 404 } ) ;
2023-03-01 15:32:39 -08:00
const response = snapshotServer . serveSnapshot ( relativePath , url . searchParams , url . href ) ;
if ( isDeployedAsHttps )
response . headers . set ( 'Content-Security-Policy' , 'upgrade-insecure-requests' ) ;
return response ;
2021-10-13 12:31:54 -08:00
}
if ( relativePath . startsWith ( '/sha1/' ) ) {
2023-05-19 15:18:18 -07:00
// Sha1 for sources is based on the file path, can't load it of a random model.
const traceUrls = clientIdToTraceUrls . get ( event . clientId ) ;
for ( const [ trace , { traceModel } ] of loadedTraces ) {
// We will accept explicit ?trace= value as well as the clientId associated with the trace.
if ( traceUrl !== trace && ! traceUrls . includes ( trace ) )
continue ;
2021-10-13 12:31:54 -08:00
const blob = await traceModel ! . resourceForSha1 ( relativePath . slice ( '/sha1/' . length ) ) ;
if ( blob )
return new Response ( blob , { status : 200 } ) ;
}
return new Response ( null , { status : 404 } ) ;
}
// Fallback to network.
2021-10-12 13:42:50 -08:00
return fetch ( event . request ) ;
2021-10-11 19:52:28 -08:00
}
2023-01-30 19:07:52 -08:00
const snapshotUrl = unwrapPopoutUrl ( client ! . url ) ;
2021-11-09 15:12:37 -08:00
const traceUrl = new URL ( snapshotUrl ) . searchParams . get ( 'trace' ) ! ;
const { snapshotServer } = loadedTraces . get ( traceUrl ) || { } ;
2021-10-12 13:42:50 -08:00
if ( ! snapshotServer )
return new Response ( null , { status : 404 } ) ;
2023-02-28 17:08:46 -08:00
const lookupUrls = [ request . url ] ;
2023-03-01 15:32:39 -08:00
if ( isDeployedAsHttps && request . url . startsWith ( 'https://' ) )
2023-02-28 17:08:46 -08:00
lookupUrls . push ( request . url . replace ( /^https/ , 'http' ) ) ;
return snapshotServer . serveResource ( lookupUrls , snapshotUrl ) ;
2021-10-13 12:31:54 -08:00
}
async function gc() {
2022-04-02 03:20:05 +08:00
const clients = await self . clients . matchAll ( ) ;
2021-10-13 12:31:54 -08:00
const usedTraces = new Set < string > ( ) ;
2022-04-02 03:20:05 +08:00
2022-08-15 20:54:57 -07:00
for ( const [ clientId , traceUrls ] of clientIdToTraceUrls ) {
2022-04-02 03:20:05 +08:00
// @ts-ignore
if ( ! clients . find ( c = > c . id === clientId ) )
2022-08-15 20:54:57 -07:00
clientIdToTraceUrls . deleteAll ( clientId ) ;
2022-04-02 03:20:05 +08:00
else
2022-08-15 20:54:57 -07:00
traceUrls . forEach ( url = > usedTraces . add ( url ) ) ;
2021-10-13 12:31:54 -08:00
}
for ( const traceUrl of loadedTraces . keys ( ) ) {
if ( ! usedTraces . has ( traceUrl ) )
loadedTraces . delete ( traceUrl ) ;
}
2021-10-11 19:52:28 -08:00
}
2021-10-12 13:42:50 -08:00
// @ts-ignore
self . addEventListener ( 'fetch' , function ( event : FetchEvent ) {
2021-10-11 19:52:28 -08:00
event . respondWith ( doFetch ( event ) ) ;
} ) ;