Complete Chrome DevTools Protocol JSON definitions and TypeScript types for building debugging tools and browser automation
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Modern web platform APIs including WebAuthn, Service Workers, Progressive Web App features, and other advanced browser capabilities. This covers domains that enable debugging and testing of cutting-edge web technologies.
WebAuthn/FIDO2 authentication debugging for testing passwordless authentication flows.
namespace Protocol.WebAuthn {
type AuthenticatorId = string;
type AuthenticatorProtocol = ('u2f' | 'ctap2');
type AuthenticatorTransport = ('usb' | 'nfc' | 'ble' | 'cable' | 'internal');
type Ctap2Version = ('ctap2_0' | 'ctap2_1');
interface VirtualAuthenticatorOptions {
protocol: AuthenticatorProtocol;
ctap2Version?: Ctap2Version;
transport: AuthenticatorTransport;
hasResidentKey?: boolean;
hasUserVerification?: boolean;
hasLargeBlob?: boolean;
hasCredBlob?: boolean;
hasMinPinLength?: boolean;
hasPrf?: boolean;
automaticPresenceSimulation?: boolean;
isUserVerified?: boolean;
defaultBackupEligibility?: boolean;
defaultBackupState?: boolean;
}
interface Credential {
credentialId: string;
isResidentCredential: boolean;
rpId?: string;
privateKey: string;
userHandle?: string;
signCount: integer;
largeBlob?: string;
backupEligibility?: boolean;
backupState?: boolean;
}
interface AddVirtualAuthenticatorRequest {
options: VirtualAuthenticatorOptions;
}
interface AddVirtualAuthenticatorResponse {
authenticatorId: AuthenticatorId;
}
interface RemoveVirtualAuthenticatorRequest {
authenticatorId: AuthenticatorId;
}
interface AddCredentialRequest {
authenticatorId: AuthenticatorId;
credential: Credential;
}
interface GetCredentialRequest {
authenticatorId: AuthenticatorId;
credentialId: string;
}
interface GetCredentialResponse {
credential: Credential;
}
interface GetCredentialsRequest {
authenticatorId: AuthenticatorId;
}
interface GetCredentialsResponse {
credentials: Credential[];
}
interface RemoveCredentialRequest {
authenticatorId: AuthenticatorId;
credentialId: string;
}
interface ClearCredentialsRequest {
authenticatorId: AuthenticatorId;
}
interface SetUserVerifiedRequest {
authenticatorId: AuthenticatorId;
isUserVerified: boolean;
}
interface SetAutomaticPresenceSimulationRequest {
authenticatorId: AuthenticatorId;
enabled: boolean;
}
interface CredentialAddedEvent {
authenticatorId: AuthenticatorId;
credential: Credential;
}
interface CredentialAssertedEvent {
authenticatorId: AuthenticatorId;
credential: Credential;
}
}Usage Example:
import Protocol from "devtools-protocol/types/protocol";
class WebAuthnTester {
private authenticators: Map<string, Protocol.WebAuthn.AuthenticatorId> = new Map();
async setupVirtualAuthenticator(name: string, options: Partial<Protocol.WebAuthn.VirtualAuthenticatorOptions> = {}): Promise<Protocol.WebAuthn.AuthenticatorId> {
const defaultOptions: Protocol.WebAuthn.VirtualAuthenticatorOptions = {
protocol: 'ctap2',
ctap2Version: 'ctap2_1',
transport: 'internal',
hasResidentKey: true,
hasUserVerification: true,
automaticPresenceSimulation: true,
isUserVerified: true,
...options
};
const request: Protocol.WebAuthn.AddVirtualAuthenticatorRequest = {
options: defaultOptions
};
// Implementation would send WebAuthn.addVirtualAuthenticator
const response: Protocol.WebAuthn.AddVirtualAuthenticatorResponse = {
authenticatorId: `auth_${Date.now()}`
};
this.authenticators.set(name, response.authenticatorId);
return response.authenticatorId;
}
async addTestCredential(authenticatorName: string, credential: Partial<Protocol.WebAuthn.Credential>): Promise<void> {
const authenticatorId = this.authenticators.get(authenticatorName);
if (!authenticatorId) {
throw new Error(`Authenticator ${authenticatorName} not found`);
}
const fullCredential: Protocol.WebAuthn.Credential = {
credentialId: credential.credentialId || this.generateCredentialId(),
isResidentCredential: credential.isResidentCredential ?? true,
rpId: credential.rpId || 'example.com',
privateKey: credential.privateKey || this.generatePrivateKey(),
userHandle: credential.userHandle || this.generateUserHandle(),
signCount: credential.signCount ?? 0,
backupEligibility: credential.backupEligibility ?? false,
backupState: credential.backupState ?? false
};
const request: Protocol.WebAuthn.AddCredentialRequest = {
authenticatorId,
credential: fullCredential
};
// Implementation would send WebAuthn.addCredential
}
async testAuthenticationFlow(authenticatorName: string, rpId: string): Promise<AuthenticationTestResult> {
const authenticatorId = this.authenticators.get(authenticatorName);
if (!authenticatorId) {
throw new Error(`Authenticator ${authenticatorName} not found`);
}
// Get all credentials for this authenticator
const credentialsRequest: Protocol.WebAuthn.GetCredentialsRequest = {
authenticatorId
};
// Implementation would get credentials and test authentication
const result: AuthenticationTestResult = {
success: true,
credentialCount: 1,
authenticatorResponse: "mock_response",
errors: []
};
return result;
}
setupWebAuthnListeners(): void {
this.onCredentialAdded = (event: Protocol.WebAuthn.CredentialAddedEvent) => {
console.log(`Credential added to authenticator ${event.authenticatorId}:`, event.credential.credentialId);
};
this.onCredentialAsserted = (event: Protocol.WebAuthn.CredentialAssertedEvent) => {
console.log(`Credential asserted on authenticator ${event.authenticatorId}:`, event.credential.credentialId);
};
}
async removeVirtualAuthenticator(name: string): Promise<void> {
const authenticatorId = this.authenticators.get(name);
if (!authenticatorId) return;
const request: Protocol.WebAuthn.RemoveVirtualAuthenticatorRequest = {
authenticatorId
};
// Implementation would send WebAuthn.removeVirtualAuthenticator
this.authenticators.delete(name);
}
private generateCredentialId(): string {
return btoa(String.fromCharCode(...Array.from(crypto.getRandomValues(new Uint8Array(32)))));
}
private generatePrivateKey(): string {
return btoa(String.fromCharCode(...Array.from(crypto.getRandomValues(new Uint8Array(32)))));
}
private generateUserHandle(): string {
return btoa(String.fromCharCode(...Array.from(crypto.getRandomValues(new Uint8Array(16)))));
}
}
interface AuthenticationTestResult {
success: boolean;
credentialCount: number;
authenticatorResponse: string;
errors: string[];
}Service worker debugging and management for testing offline functionality and background processing.
namespace Protocol.ServiceWorker {
type RegistrationID = string;
type ServiceWorkerVersionRunningStatus = ('stopped' | 'starting' | 'running' | 'stopping');
type ServiceWorkerVersionStatus = ('new' | 'installing' | 'installed' | 'activating' | 'activated' | 'redundant');
interface ServiceWorkerRegistration {
registrationId: RegistrationID;
scopeURL: string;
isDeleted: boolean;
}
interface ServiceWorkerVersion {
versionId: string;
registrationId: RegistrationID;
scriptURL: string;
runningStatus: ServiceWorkerVersionRunningStatus;
status: ServiceWorkerVersionStatus;
scriptLastModified?: number;
scriptResponseTime?: number;
controlledClients?: Target.TargetID[];
targetId?: Target.TargetID;
routerRules?: string;
}
interface ServiceWorkerErrorMessage {
errorMessage: string;
registrationId: RegistrationID;
versionId: string;
sourceURL: string;
lineNumber: integer;
columnNumber: integer;
}
interface GetVersionsResponse {
versions: ServiceWorkerVersion[];
}
interface StartWorkerRequest {
scopeURL: string;
}
interface StopWorkerRequest {
versionId: string;
}
interface InspectWorkerRequest {
versionId: string;
}
interface SetForceUpdateOnPageLoadRequest {
forceUpdateOnPageLoad: boolean;
}
interface DeliverPushMessageRequest {
origin: string;
registrationId: RegistrationID;
data: string;
}
interface DispatchSyncEventRequest {
origin: string;
registrationId: RegistrationID;
tag: string;
lastChance: boolean;
}
interface DispatchPeriodicSyncEventRequest {
origin: string;
registrationId: RegistrationID;
tag: string;
}
interface SkipWaitingRequest {
scopeURL: string;
}
interface UnregisterRequest {
scopeURL: string;
}
interface UpdateRegistrationRequest {
scopeURL: string;
}
interface WorkerErrorReportedEvent {
errorMessage: ServiceWorkerErrorMessage;
}
interface WorkerRegistrationUpdatedEvent {
registrations: ServiceWorkerRegistration[];
}
interface WorkerVersionUpdatedEvent {
versions: ServiceWorkerVersion[];
}
}Usage Example:
import Protocol from "devtools-protocol/types/protocol";
class ServiceWorkerTester {
private registrations: Map<string, Protocol.ServiceWorker.ServiceWorkerRegistration> = new Map();
private versions: Map<string, Protocol.ServiceWorker.ServiceWorkerVersion> = new Map();
async getAllServiceWorkers(): Promise<ServiceWorkerAnalysis> {
// Get all service worker versions
const versionsResponse = await this.getVersions();
const analysis: ServiceWorkerAnalysis = {
totalRegistrations: 0,
totalVersions: versionsResponse.versions.length,
activeWorkers: [],
inactiveWorkers: [],
erroredWorkers: []
};
versionsResponse.versions.forEach(version => {
this.versions.set(version.versionId, version);
if (version.runningStatus === 'running' && version.status === 'activated') {
analysis.activeWorkers.push(version);
} else if (version.status === 'redundant') {
analysis.inactiveWorkers.push(version);
}
});
// Group by registrations
const registrationMap = new Map<string, Protocol.ServiceWorker.ServiceWorkerVersion[]>();
versionsResponse.versions.forEach(version => {
if (!registrationMap.has(version.registrationId)) {
registrationMap.set(version.registrationId, []);
}
registrationMap.get(version.registrationId)!.push(version);
});
analysis.totalRegistrations = registrationMap.size;
return analysis;
}
private async getVersions(): Promise<Protocol.ServiceWorker.GetVersionsResponse> {
// Implementation would send ServiceWorker.getVersions
return {
versions: [
{
versionId: "version_1",
registrationId: "reg_1",
scriptURL: "https://example.com/sw.js",
runningStatus: 'running',
status: 'activated',
controlledClients: ["client_1", "client_2"]
}
]
};
}
async testServiceWorkerLifecycle(scopeURL: string): Promise<LifecycleTestResult> {
const result: LifecycleTestResult = {
registrationSucceeded: false,
installationSucceeded: false,
activationSucceeded: false,
errors: []
};
try {
// Start the worker
const startRequest: Protocol.ServiceWorker.StartWorkerRequest = { scopeURL };
// Implementation would send ServiceWorker.startWorker
result.registrationSucceeded = true;
// Wait for installation and activation events
await this.waitForWorkerStates(scopeURL, ['installed', 'activated']);
result.installationSucceeded = true;
result.activationSucceeded = true;
} catch (error) {
result.errors.push(error.message);
}
return result;
}
async testPushMessage(origin: string, registrationId: string, data: any): Promise<PushTestResult> {
const request: Protocol.ServiceWorker.DeliverPushMessageRequest = {
origin,
registrationId,
data: JSON.stringify(data)
};
try {
// Implementation would send ServiceWorker.deliverPushMessage
return {
success: true,
delivered: true,
response: "Push message delivered successfully"
};
} catch (error) {
return {
success: false,
delivered: false,
error: error.message
};
}
}
async testSyncEvent(origin: string, registrationId: string, tag: string): Promise<SyncTestResult> {
const request: Protocol.ServiceWorker.DispatchSyncEventRequest = {
origin,
registrationId,
tag,
lastChance: false
};
try {
// Implementation would send ServiceWorker.dispatchSyncEvent
return {
success: true,
executed: true,
tag
};
} catch (error) {
return {
success: false,
executed: false,
tag,
error: error.message
};
}
}
async updateServiceWorker(scopeURL: string): Promise<void> {
const request: Protocol.ServiceWorker.UpdateRegistrationRequest = { scopeURL };
// Implementation would send ServiceWorker.updateRegistration
}
async unregisterServiceWorker(scopeURL: string): Promise<void> {
const request: Protocol.ServiceWorker.UnregisterRequest = { scopeURL };
// Implementation would send ServiceWorker.unregister
}
setupServiceWorkerListeners(): void {
this.onWorkerErrorReported = (event: Protocol.ServiceWorker.WorkerErrorReportedEvent) => {
console.error(`Service Worker error in ${event.errorMessage.sourceURL}:${event.errorMessage.lineNumber}: ${event.errorMessage.errorMessage}`);
};
this.onWorkerRegistrationUpdated = (event: Protocol.ServiceWorker.WorkerRegistrationUpdatedEvent) => {
console.log(`Service Worker registrations updated: ${event.registrations.length} registrations`);
event.registrations.forEach(reg => {
this.registrations.set(reg.scopeURL, reg);
});
};
this.onWorkerVersionUpdated = (event: Protocol.ServiceWorker.WorkerVersionUpdatedEvent) => {
console.log(`Service Worker versions updated: ${event.versions.length} versions`);
event.versions.forEach(version => {
this.versions.set(version.versionId, version);
});
};
}
private async waitForWorkerStates(scopeURL: string, targetStates: Protocol.ServiceWorker.ServiceWorkerVersionStatus[]): Promise<void> {
// Implementation would wait for specific worker states
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error(`Timeout waiting for worker states: ${targetStates.join(', ')}`));
}, 10000);
// Monitor version updates and resolve when target states are reached
const checkStates = () => {
const relevantVersions = Array.from(this.versions.values())
.filter(v => v.scriptURL.includes(scopeURL));
const hasTargetStates = targetStates.every(state =>
relevantVersions.some(v => v.status === state)
);
if (hasTargetStates) {
clearTimeout(timeout);
resolve();
}
};
// In a real implementation, this would listen to worker events
setTimeout(checkStates, 1000);
});
}
}
interface ServiceWorkerAnalysis {
totalRegistrations: number;
totalVersions: number;
activeWorkers: Protocol.ServiceWorker.ServiceWorkerVersion[];
inactiveWorkers: Protocol.ServiceWorker.ServiceWorkerVersion[];
erroredWorkers: Protocol.ServiceWorker.ServiceWorkerVersion[];
}
interface LifecycleTestResult {
registrationSucceeded: boolean;
installationSucceeded: boolean;
activationSucceeded: boolean;
errors: string[];
}
interface PushTestResult {
success: boolean;
delivered: boolean;
response?: string;
error?: string;
}
interface SyncTestResult {
success: boolean;
executed: boolean;
tag: string;
error?: string;
}Progressive Web App features and debugging for testing app-like functionality.
namespace Protocol.PWA {
interface FileHandler {
action: string;
accepts: FileHandlerAccept[];
}
interface FileHandlerAccept {
mediaType: string;
fileExtensions: string[];
}
interface DisplayMode = ('fullscreen' | 'standalone' | 'minimal-ui' | 'browser');
interface GetOsAppStateRequest {
manifestId: string;
}
interface GetOsAppStateResponse {
badgeCount: integer;
fileHandlers: FileHandler[];
}
interface InstallRequest {
manifestId: string;
installUrlOrBundleUrl?: string;
}
interface UninstallRequest {
manifestId: string;
}
interface LaunchFilesInAppRequest {
manifestId: string;
files: string[];
}
interface OpenCurrentPageInAppRequest {
manifestId: string;
}
interface ChangeAppUserSettingsRequest {
manifestId: string;
linkCapturing?: boolean;
displayMode?: DisplayMode;
}
}Media element debugging and inspection for analyzing audio/video playback.
namespace Protocol.Media {
type PlayerId = string;
type Timestamp = number;
interface PlayerMessage {
level: ('error' | 'warning' | 'info' | 'debug');
message: string;
}
interface PlayerProperty {
name: string;
value: string;
}
interface PlayerEvent {
timestamp: Timestamp;
value: string;
}
interface PlayerErrorSourceLocation {
file: string;
line: integer;
}
interface PlayerError {
errorType: string;
code: integer;
stack: PlayerErrorSourceLocation[];
cause: PlayerError[];
data: Record<string, any>;
}
interface PlayersCreatedEvent {
players: PlayerId[];
}
interface PlayerPropertiesChangedEvent {
playerId: PlayerId;
properties: PlayerProperty[];
}
interface PlayerEventsAddedEvent {
playerId: PlayerId;
events: PlayerEvent[];
}
interface PlayerMessagesLoggedEvent {
playerId: PlayerId;
messages: PlayerMessage[];
}
interface PlayerErrorsRaisedEvent {
playerId: PlayerId;
errors: PlayerError[];
}
}Web Audio API debugging for analyzing audio context and node graphs.
namespace Protocol.WebAudio {
type GraphObjectId = string;
type ContextType = ('realtime' | 'offline');
type ContextState = ('suspended' | 'running' | 'closed');
type NodeType = string;
type ChannelCountMode = ('max' | 'clamped-max' | 'explicit');
type ChannelInterpretation = ('speakers' | 'discrete');
interface BaseAudioContext {
contextId: GraphObjectId;
contextType: ContextType;
contextState: ContextState;
realtimeData?: ContextRealtimeData;
callbackBufferSize?: number;
maxOutputChannelCount?: number;
sampleRate?: number;
}
interface AudioListener {
listenerId: GraphObjectId;
contextId: GraphObjectId;
}
interface AudioNode {
nodeId: GraphObjectId;
contextId: GraphObjectId;
nodeType: NodeType;
numberOfInputs: number;
numberOfOutputs: number;
channelCount?: number;
channelCountMode?: ChannelCountMode;
channelInterpretation?: ChannelInterpretation;
}
interface AudioParam {
paramId: GraphObjectId;
nodeId: GraphObjectId;
contextId: GraphObjectId;
paramType: string;
rate: ('a-rate' | 'k-rate');
defaultValue: number;
minValue: number;
maxValue: number;
}
interface ContextRealtimeData {
currentTime: number;
renderCapacity: number;
callbackIntervalMean: number;
callbackIntervalVariance: number;
}
interface GetRealtimeDataRequest {
contextId: GraphObjectId;
}
interface GetRealtimeDataResponse {
realtimeData: ContextRealtimeData;
}
interface ContextCreatedEvent {
context: BaseAudioContext;
}
interface ContextWillBeDestroyedEvent {
contextId: GraphObjectId;
}
interface ContextChangedEvent {
context: BaseAudioContext;
}
interface AudioListenerCreatedEvent {
listener: AudioListener;
}
interface AudioListenerWillBeDestroyedEvent {
contextId: GraphObjectId;
listenerId: GraphObjectId;
}
interface AudioNodeCreatedEvent {
node: AudioNode;
}
interface AudioNodeWillBeDestroyedEvent {
contextId: GraphObjectId;
nodeId: GraphObjectId;
}
interface AudioParamCreatedEvent {
param: AudioParam;
}
interface AudioParamWillBeDestroyedEvent {
contextId: GraphObjectId;
nodeId: GraphObjectId;
paramId: GraphObjectId;
}
interface NodesConnectedEvent {
contextId: GraphObjectId;
sourceId: GraphObjectId;
destinationId: GraphObjectId;
sourceOutputIndex?: number;
destinationInputIndex?: number;
}
interface NodesDisconnectedEvent {
contextId: GraphObjectId;
sourceId: GraphObjectId;
destinationId: GraphObjectId;
sourceOutputIndex?: number;
destinationInputIndex?: number;
}
interface NodeParamConnectedEvent {
contextId: GraphObjectId;
sourceId: GraphObjectId;
destinationId: GraphObjectId;
sourceOutputIndex?: number;
}
interface NodeParamDisconnectedEvent {
contextId: GraphObjectId;
sourceId: GraphObjectId;
destinationId: GraphObjectId;
sourceOutputIndex?: number;
}
}import Protocol from "devtools-protocol/types/protocol";
class PWATester {
private webAuthnTester: WebAuthnTester;
private serviceWorkerTester: ServiceWorkerTester;
constructor() {
this.webAuthnTester = new WebAuthnTester();
this.serviceWorkerTester = new ServiceWorkerTester();
}
async performCompletePWATest(manifestId: string, origin: string): Promise<PWATestResults> {
const results: PWATestResults = {
manifestId,
origin,
serviceWorkerTest: null,
webAuthnTest: null,
offlineTest: null,
installabilityTest: null,
pushNotificationTest: null,
overallScore: 0,
recommendations: []
};
try {
// Test Service Worker functionality
results.serviceWorkerTest = await this.testServiceWorkerFeatures(origin);
// Test WebAuthn if available
results.webAuthnTest = await this.testWebAuthnFeatures();
// Test offline functionality
results.offlineTest = await this.testOfflineFunctionality(origin);
// Test PWA installability
results.installabilityTest = await this.testInstallability(manifestId);
// Test push notifications
results.pushNotificationTest = await this.testPushNotifications(origin);
results.overallScore = this.calculateOverallScore(results);
results.recommendations = this.generateRecommendations(results);
} catch (error) {
console.error("PWA testing failed:", error);
results.recommendations.push("PWA testing encountered errors - check console for details");
}
return results;
}
private async testServiceWorkerFeatures(origin: string): Promise<ServiceWorkerTestResults> {
const analysis = await this.serviceWorkerTester.getAllServiceWorkers();
// Test service worker lifecycle
const lifecycleResults = await this.serviceWorkerTester.testServiceWorkerLifecycle(`${origin}/sw.js`);
return {
hasServiceWorker: analysis.activeWorkers.length > 0,
isActive: analysis.activeWorkers.length > 0,
lifecycleTest: lifecycleResults,
controlledClients: analysis.activeWorkers.reduce((sum, sw) => sum + (sw.controlledClients?.length || 0), 0)
};
}
private async testWebAuthnFeatures(): Promise<WebAuthnTestResults> {
try {
// Setup virtual authenticator for testing
const authenticatorId = await this.webAuthnTester.setupVirtualAuthenticator('test-auth', {
protocol: 'ctap2',
transport: 'internal',
hasUserVerification: true
});
// Add test credential
await this.webAuthnTester.addTestCredential('test-auth', {
rpId: 'example.com',
isResidentCredential: true
});
// Test authentication flow
const authResult = await this.webAuthnTester.testAuthenticationFlow('test-auth', 'example.com');
// Cleanup
await this.webAuthnTester.removeVirtualAuthenticator('test-auth');
return {
supported: true,
authenticationWorking: authResult.success,
credentialCount: authResult.credentialCount,
errors: authResult.errors
};
} catch (error) {
return {
supported: false,
authenticationWorking: false,
credentialCount: 0,
errors: [error.message]
};
}
}
private async testOfflineFunctionality(origin: string): Promise<OfflineTestResults> {
// This would involve testing cache strategies and offline page functionality
return {
cacheStrategyWorks: true,
offlinePageExists: true,
criticalResourcesCached: true,
offlineNavigation: true
};
}
private async testInstallability(manifestId: string): Promise<InstallabilityTestResults> {
try {
// Get PWA state
const stateRequest: Protocol.PWA.GetOsAppStateRequest = { manifestId };
// Implementation would check PWA state
return {
isInstallable: true,
hasValidManifest: true,
hasServiceWorker: true,
httpsRequired: true,
meets256IconRequirement: true
};
} catch (error) {
return {
isInstallable: false,
hasValidManifest: false,
hasServiceWorker: false,
httpsRequired: false,
meets256IconRequirement: false,
error: error.message
};
}
}
private async testPushNotifications(origin: string): Promise<PushTestResults> {
const serviceWorkers = await this.serviceWorkerTester.getAllServiceWorkers();
if (serviceWorkers.activeWorkers.length === 0) {
return {
supported: false,
subscriptionWorks: false,
messageDelivery: false,
error: "No active service worker found"
};
}
const activeWorker = serviceWorkers.activeWorkers[0];
try {
const pushResult = await this.serviceWorkerTester.testPushMessage(
origin,
activeWorker.registrationId,
{ title: "Test Push", body: "This is a test push notification" }
);
return {
supported: true,
subscriptionWorks: true,
messageDelivery: pushResult.success,
error: pushResult.error
};
} catch (error) {
return {
supported: false,
subscriptionWorks: false,
messageDelivery: false,
error: error.message
};
}
}
private calculateOverallScore(results: PWATestResults): number {
let score = 0;
let maxScore = 0;
// Service Worker (20 points)
maxScore += 20;
if (results.serviceWorkerTest?.hasServiceWorker) score += 20;
// WebAuthn (10 points)
maxScore += 10;
if (results.webAuthnTest?.supported) score += 10;
// Offline functionality (25 points)
maxScore += 25;
if (results.offlineTest?.cacheStrategyWorks) score += 10;
if (results.offlineTest?.offlinePageExists) score += 10;
if (results.offlineTest?.criticalResourcesCached) score += 5;
// Installability (25 points)
maxScore += 25;
if (results.installabilityTest?.isInstallable) score += 25;
// Push notifications (20 points)
maxScore += 20;
if (results.pushNotificationTest?.messageDelivery) score += 20;
return Math.round((score / maxScore) * 100);
}
private generateRecommendations(results: PWATestResults): string[] {
const recommendations: string[] = [];
if (!results.serviceWorkerTest?.hasServiceWorker) {
recommendations.push("Add a Service Worker to enable offline functionality and push notifications");
}
if (!results.webAuthnTest?.supported) {
recommendations.push("Consider implementing WebAuthn for passwordless authentication");
}
if (!results.offlineTest?.offlinePageExists) {
recommendations.push("Create an offline page to improve user experience when network is unavailable");
}
if (!results.installabilityTest?.isInstallable) {
recommendations.push("Ensure your PWA meets installability requirements");
}
if (!results.pushNotificationTest?.supported) {
recommendations.push("Implement push notifications to re-engage users");
}
return recommendations;
}
}
interface PWATestResults {
manifestId: string;
origin: string;
serviceWorkerTest: ServiceWorkerTestResults | null;
webAuthnTest: WebAuthnTestResults | null;
offlineTest: OfflineTestResults | null;
installabilityTest: InstallabilityTestResults | null;
pushNotificationTest: PushTestResults | null;
overallScore: number;
recommendations: string[];
}
interface ServiceWorkerTestResults {
hasServiceWorker: boolean;
isActive: boolean;
lifecycleTest: LifecycleTestResult;
controlledClients: number;
}
interface WebAuthnTestResults {
supported: boolean;
authenticationWorking: boolean;
credentialCount: number;
errors: string[];
}
interface OfflineTestResults {
cacheStrategyWorks: boolean;
offlinePageExists: boolean;
criticalResourcesCached: boolean;
offlineNavigation: boolean;
}
interface InstallabilityTestResults {
isInstallable: boolean;
hasValidManifest: boolean;
hasServiceWorker: boolean;
httpsRequired: boolean;
meets256IconRequirement: boolean;
error?: string;
}
interface PushTestResults {
supported: boolean;
subscriptionWorks: boolean;
messageDelivery: boolean;
error?: string;
}import Protocol from "devtools-protocol/types/protocol";
class WebAudioAnalyzer {
private contexts: Map<string, Protocol.WebAudio.BaseAudioContext> = new Map();
private nodes: Map<string, Protocol.WebAudio.AudioNode> = new Map();
private connections: Array<{ source: string; destination: string }> = [];
setupWebAudioListeners(): void {
this.onContextCreated = (event: Protocol.WebAudio.ContextCreatedEvent) => {
console.log(`Audio context created: ${event.context.contextId} (${event.context.contextType})`);
this.contexts.set(event.context.contextId, event.context);
};
this.onAudioNodeCreated = (event: Protocol.WebAudio.AudioNodeCreatedEvent) => {
console.log(`Audio node created: ${event.node.nodeType} (${event.node.nodeId})`);
this.nodes.set(event.node.nodeId, event.node);
};
this.onNodesConnected = (event: Protocol.WebAudio.NodesConnectedEvent) => {
console.log(`Nodes connected: ${event.sourceId} -> ${event.destinationId}`);
this.connections.push({ source: event.sourceId, destination: event.destinationId });
};
}
async analyzeAudioGraph(): Promise<AudioGraphAnalysis> {
const analysis: AudioGraphAnalysis = {
contexts: Array.from(this.contexts.values()),
nodes: Array.from(this.nodes.values()),
connections: this.connections,
performance: []
};
// Analyze performance for realtime contexts
for (const context of analysis.contexts) {
if (context.contextType === 'realtime') {
const performanceData = await this.getRealtimeData(context.contextId);
analysis.performance.push(performanceData);
}
}
return analysis;
}
private async getRealtimeData(contextId: string): Promise<Protocol.WebAudio.ContextRealtimeData> {
const request: Protocol.WebAudio.GetRealtimeDataRequest = { contextId };
// Implementation would send WebAudio.getRealtimeData
return {
currentTime: 10.5,
renderCapacity: 0.15,
callbackIntervalMean: 0.0023,
callbackIntervalVariance: 0.0001
};
}
generateAudioGraphVisualization(): AudioGraphVisualization {
const nodesByContext = new Map<string, Protocol.WebAudio.AudioNode[]>();
// Group nodes by context
for (const node of this.nodes.values()) {
if (!nodesByContext.has(node.contextId)) {
nodesByContext.set(node.contextId, []);
}
nodesByContext.get(node.contextId)!.push(node);
}
const visualization: AudioGraphVisualization = {
contexts: [],
issues: []
};
// Create visualization for each context
for (const [contextId, nodes] of nodesByContext.entries()) {
const context = this.contexts.get(contextId);
if (!context) continue;
const contextViz: ContextVisualization = {
contextId,
contextType: context.contextType,
nodes: nodes.map(node => ({
id: node.nodeId,
type: node.nodeType,
inputs: node.numberOfInputs,
outputs: node.numberOfOutputs
})),
connections: this.connections.filter(conn =>
nodes.some(n => n.nodeId === conn.source) &&
nodes.some(n => n.nodeId === conn.destination)
)
};
visualization.contexts.push(contextViz);
// Check for potential issues
if (nodes.length > 50) {
visualization.issues.push(`Context ${contextId} has ${nodes.length} nodes - consider optimization`);
}
}
return visualization;
}
}
interface AudioGraphAnalysis {
contexts: Protocol.WebAudio.BaseAudioContext[];
nodes: Protocol.WebAudio.AudioNode[];
connections: Array<{ source: string; destination: string }>;
performance: Protocol.WebAudio.ContextRealtimeData[];
}
interface AudioGraphVisualization {
contexts: ContextVisualization[];
issues: string[];
}
interface ContextVisualization {
contextId: string;
contextType: Protocol.WebAudio.ContextType;
nodes: Array<{
id: string;
type: string;
inputs: number;
outputs: number;
}>;
connections: Array<{ source: string; destination: string }>;
}Install with Tessl CLI
npx tessl i tessl/npm-devtools-protocol