UNPKG

chrome-devtools-frontend

Version:
221 lines (191 loc) • 8.25 kB
// Copyright 2026 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import * as Common from '../../core/common/common.js'; import * as SDK from '../../core/sdk/sdk.js'; import type * as Protocol from '../../generated/protocol.js'; interface EventWithTimestamp { event: Protocol.Network.DeviceBoundSessionEventOccurredEvent; timestamp: Date; } export interface SessionAndEvents { session?: Protocol.Network.DeviceBoundSession; isSessionTerminated: boolean; hasErrors: boolean; eventsById: Map<string, EventWithTimestamp>; } type SessionIdToSessionMap = Map<string|undefined, SessionAndEvents>; export class DeviceBoundSessionsModel extends Common.ObjectWrapper.ObjectWrapper<DeviceBoundSessionModelEventTypes> implements SDK.TargetManager.SDKModelObserver<SDK.NetworkManager.NetworkManager> { #siteSessions = new Map<string, SessionIdToSessionMap>(); #visibleSites = new Set<string>(); constructor() { super(); SDK.TargetManager.TargetManager.instance().observeModels(SDK.NetworkManager.NetworkManager, this, {scoped: true}); } modelAdded(networkManager: SDK.NetworkManager.NetworkManager): void { networkManager.addEventListener(SDK.NetworkManager.Events.DeviceBoundSessionsAdded, this.#onSessionsSet, this); networkManager.addEventListener( SDK.NetworkManager.Events.DeviceBoundSessionEventOccurred, this.#onEventOccurred, this); void networkManager.enableDeviceBoundSessions(); } modelRemoved(networkManager: SDK.NetworkManager.NetworkManager): void { networkManager.removeEventListener(SDK.NetworkManager.Events.DeviceBoundSessionsAdded, this.#onSessionsSet, this); networkManager.removeEventListener( SDK.NetworkManager.Events.DeviceBoundSessionEventOccurred, this.#onEventOccurred, this); } addVisibleSite(site: string): void { if (this.#visibleSites.has(site)) { return; } this.#visibleSites.add(site); this.dispatchEventToListeners(DeviceBoundSessionModelEvents.ADD_VISIBLE_SITE, {site}); } clearVisibleSites(): void { if (this.getPreserveLogSetting().get()) { return; } this.#visibleSites.clear(); this.dispatchEventToListeners(DeviceBoundSessionModelEvents.CLEAR_VISIBLE_SITES); } clearEvents(): void { if (this.getPreserveLogSetting().get()) { return; } const emptySessions = new Map<string, Array<string|undefined>>(); const noLongerFailedSessions = new Map<string, Array<string|undefined>>(); const emptySites = new Set<string>(); for (const [site, sessionIdToSessionMap] of [...this.#siteSessions]) { let emptySessionsSiteEntry = emptySessions.get(site); let noLongerFailedSessionsSiteEntry = noLongerFailedSessions.get(site); for (const [sessionId, sessionAndEvents] of sessionIdToSessionMap) { sessionAndEvents.eventsById.clear(); if (sessionAndEvents.hasErrors) { sessionAndEvents.hasErrors = false; if (!noLongerFailedSessionsSiteEntry) { noLongerFailedSessionsSiteEntry = []; noLongerFailedSessions.set(site, noLongerFailedSessionsSiteEntry); } noLongerFailedSessionsSiteEntry.push(sessionId); } if (sessionAndEvents.session) { continue; } // Remove empty sessions. sessionIdToSessionMap.delete(sessionId); if (!emptySessionsSiteEntry) { emptySessionsSiteEntry = []; emptySessions.set(site, emptySessionsSiteEntry); } emptySessionsSiteEntry.push(sessionId); } // Remove empty sites. if (sessionIdToSessionMap.size === 0) { this.#siteSessions.delete(site); emptySites.add(site); } } this.dispatchEventToListeners( DeviceBoundSessionModelEvents.CLEAR_EVENTS, {emptySessions, emptySites, noLongerFailedSessions}); } isSiteVisible(site: string): boolean { return this.#visibleSites.has(site); } isSessionTerminated(site: string, sessionId?: string): boolean { const session = this.getSession(site, sessionId); if (session === undefined) { return false; } return session.isSessionTerminated; } sessionHasErrors(site: string, sessionId?: string): boolean { const session = this.getSession(site, sessionId); if (session === undefined) { return false; } return session.hasErrors; } getSession(site: string, sessionId?: string): SessionAndEvents|undefined { return this.#siteSessions.get(site)?.get(sessionId); } getPreserveLogSetting(): Common.Settings.Setting<boolean> { return Common.Settings.Settings.instance().createSetting('device-bound-sessions-preserve-log', false); } #onSessionsSet({data: sessions}: {data: Protocol.Network.DeviceBoundSession[]}): void { for (const session of sessions) { const sessionAndEvents = this.#ensureSiteAndSessionInitialized(session.key.site, session.key.id); sessionAndEvents.session = session; } this.dispatchEventToListeners(DeviceBoundSessionModelEvents.INITIALIZE_SESSIONS, {sessions}); } #ensureSiteAndSessionInitialized(site: string, sessionId?: string): SessionAndEvents { let sessionIdToSessionMap = this.#siteSessions.get(site); if (!sessionIdToSessionMap) { sessionIdToSessionMap = new Map(); this.#siteSessions.set(site, sessionIdToSessionMap); } let sessionAndEvent = sessionIdToSessionMap.get(sessionId); if (!sessionAndEvent) { sessionAndEvent = { session: undefined, isSessionTerminated: false, hasErrors: false, eventsById: new Map<string, EventWithTimestamp>() }; sessionIdToSessionMap.set(sessionId, sessionAndEvent); } return sessionAndEvent; } #onEventOccurred({data: event}: {data: Protocol.Network.DeviceBoundSessionEventOccurredEvent}): void { const sessionAndEvent = this.#ensureSiteAndSessionInitialized(event.site, event.sessionId); // If this eventId has already been tracked, quit early. if (sessionAndEvent.eventsById.has(event.eventId)) { return; } // Add the new event. const eventWithTimestamp = {event, timestamp: new Date()}; sessionAndEvent.eventsById.set(event.eventId, eventWithTimestamp); // Add the new session if there is one. const newSession = event.creationEventDetails?.newSession || event.refreshEventDetails?.newSession; if (newSession) { sessionAndEvent.session = newSession; } // Add the new challenge onto the session if there is one. if (event.succeeded && sessionAndEvent.session && event.challengeEventDetails) { sessionAndEvent.session.cachedChallenge = event.challengeEventDetails.challenge; } // Set the session's terminated status based on the event. if (event.succeeded) { if (event.terminationEventDetails) { sessionAndEvent.isSessionTerminated = true; } else if (event.creationEventDetails) { sessionAndEvent.isSessionTerminated = false; } } // Set that the session has errors if the latest event failed. if (!event.succeeded) { sessionAndEvent.hasErrors = true; } this.dispatchEventToListeners( DeviceBoundSessionModelEvents.EVENT_OCCURRED, {site: eventWithTimestamp.event.site, sessionId: eventWithTimestamp.event.sessionId}); } } export const enum DeviceBoundSessionModelEvents { INITIALIZE_SESSIONS = 'INITIALIZE_SESSIONS', ADD_VISIBLE_SITE = 'ADD_VISIBLE_SITE', CLEAR_VISIBLE_SITES = 'CLEAR_VISIBLE_SITES', EVENT_OCCURRED = 'EVENT_OCCURRED', CLEAR_EVENTS = 'CLEAR_EVENTS', } export interface DeviceBoundSessionModelEventTypes { [DeviceBoundSessionModelEvents.INITIALIZE_SESSIONS]: {sessions: Protocol.Network.DeviceBoundSession[]}; [DeviceBoundSessionModelEvents.ADD_VISIBLE_SITE]: {site: string}; [DeviceBoundSessionModelEvents.CLEAR_VISIBLE_SITES]: void; [DeviceBoundSessionModelEvents.EVENT_OCCURRED]: {site: string, sessionId?: string}; [DeviceBoundSessionModelEvents.CLEAR_EVENTS]: { emptySessions: Map<string, Array<string|undefined>>, emptySites: Set<string>, noLongerFailedSessions: Map<string, Array<string|undefined>>, }; }