/** * Copyright 2017 Google Inc. All rights reserved. * * 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 type {ProtocolMapping} from 'devtools-protocol/types/protocol-mapping.js'; import { type CDPEvents, CDPSession, CDPSessionEvent, } from '../api/CDPSession.js'; import {CallbackRegistry} from '../common/CallbackRegistry.js'; import {TargetCloseError} from '../common/Errors.js'; import {assert} from '../util/assert.js'; import {createProtocolErrorMessage} from '../util/ErrorLike.js'; import type {Connection} from './Connection.js'; import type {CdpTarget} from './Target.js'; /** * @internal */ export class CdpCDPSession extends CDPSession { #sessionId: string; #targetType: string; #callbacks = new CallbackRegistry(); #connection?: Connection; #parentSessionId?: string; #target?: CdpTarget; /** * @internal */ constructor( connection: Connection, targetType: string, sessionId: string, parentSessionId: string | undefined ) { super(); this.#connection = connection; this.#targetType = targetType; this.#sessionId = sessionId; this.#parentSessionId = parentSessionId; } /** * Sets the {@link CdpTarget} associated with the session instance. * * @internal */ _setTarget(target: CdpTarget): void { this.#target = target; } /** * Gets the {@link CdpTarget} associated with the session instance. * * @internal */ _target(): CdpTarget { assert(this.#target, 'Target must exist'); return this.#target; } override connection(): Connection | undefined { return this.#connection; } override parentSession(): CDPSession | undefined { if (!this.#parentSessionId) { // To make it work in Firefox that does not have parent (tab) sessions. return this; } const parent = this.#connection?.session(this.#parentSessionId); return parent ?? undefined; } override send( method: T, ...paramArgs: ProtocolMapping.Commands[T]['paramsType'] ): Promise { if (!this.#connection) { return Promise.reject( new TargetCloseError( `Protocol error (${method}): Session closed. Most likely the ${ this.#targetType } has been closed.` ) ); } // See the comment in Connection#send explaining why we do this. const params = paramArgs.length ? paramArgs[0] : undefined; return this.#connection._rawSend( this.#callbacks, method, params, this.#sessionId ); } /** * @internal */ _onMessage(object: { id?: number; method: keyof CDPEvents; params: CDPEvents[keyof CDPEvents]; error: {message: string; data: any; code: number}; result?: any; }): void { if (object.id) { if (object.error) { this.#callbacks.reject( object.id, createProtocolErrorMessage(object), object.error.message ); } else { this.#callbacks.resolve(object.id, object.result); } } else { assert(!object.id); this.emit(object.method, object.params); } } /** * Detaches the cdpSession from the target. Once detached, the cdpSession object * won't emit any events and can't be used to send messages. */ override async detach(): Promise { if (!this.#connection) { throw new Error( `Session already detached. Most likely the ${ this.#targetType } has been closed.` ); } await this.#connection.send('Target.detachFromTarget', { sessionId: this.#sessionId, }); } /** * @internal */ _onClosed(): void { this.#callbacks.clear(); this.#connection = undefined; this.emit(CDPSessionEvent.Disconnected, undefined); } /** * Returns the session's id. */ override id(): string { return this.#sessionId; } }