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 }>;
}