blob: ce0f3ac99cef326dac901c8c300fb342818c4040 [file] [log] [blame]
"use strict";
/**
* Copyright 2021 Google LLC.
* 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.
*
* @license
*/
Object.defineProperty(exports, "__esModule", { value: true });
const bidiMapper_js_1 = require("../bidiMapper/bidiMapper.js");
const cdpConnection_js_1 = require("../cdp/cdpConnection.js");
const log_js_1 = require("../utils/log.js");
const BidiParserImpl_1 = require("./BidiParserImpl");
const mapperTabPage_js_1 = require("./mapperTabPage.js");
// Initiate `setSelfTargetId` as soon as possible to prevent race condition.
const waitSelfTargetIdPromise = waitSelfTargetId();
void (async () => {
(0, mapperTabPage_js_1.generatePage)();
// Needed to filter out info related to BiDi target.
const selfTargetId = await waitSelfTargetIdPromise;
const bidiServer = await createBidiServer(selfTargetId);
(0, mapperTabPage_js_1.log)(log_js_1.LogType.system, 'Launched');
bidiServer.emitOutgoingMessage(bidiMapper_js_1.OutgoingBidiMessage.createResolved({
launched: true,
}));
})();
function createCdpConnection() {
/**
* A CdpTransport implementation that uses the window.cdp bindings
* injected by Target.exposeDevToolsProtocol.
*/
class WindowCdpTransport {
#onMessage = null;
constructor() {
window.cdp.onmessage = (message) => {
this.#onMessage?.call(null, message);
};
}
setOnMessage(onMessage) {
this.#onMessage = onMessage;
}
sendMessage(message) {
window.cdp.send(message);
}
close() {
this.#onMessage = null;
window.cdp.onmessage = null;
}
}
return new cdpConnection_js_1.CdpConnection(new WindowCdpTransport(), mapperTabPage_js_1.log);
}
function createBidiServer(selfTargetId) {
class WindowBidiTransport {
#onMessage = null;
constructor() {
window.onBidiMessage = (messageStr) => {
(0, mapperTabPage_js_1.log)(`${log_js_1.LogType.bidi}:RECV ◂`, messageStr);
let messageObject;
try {
messageObject = WindowBidiTransport.#parseBidiMessage(messageStr);
}
catch (e) {
// Transport-level error does not provide channel.
this.#respondWithError(messageStr, "invalid argument" /* ErrorCode.InvalidArgument */, e.message, null);
return;
}
this.#onMessage?.call(null, messageObject);
};
}
setOnMessage(onMessage) {
this.#onMessage = onMessage;
}
sendMessage(message) {
const messageStr = JSON.stringify(message);
window.sendBidiResponse(messageStr);
(0, mapperTabPage_js_1.log)(`${log_js_1.LogType.bidi}:SEND ▸`, messageStr);
}
close() {
this.#onMessage = null;
window.onBidiMessage = null;
}
#respondWithError(plainCommandData, errorCode, errorMessage, channel) {
const errorResponse = WindowBidiTransport.#getErrorResponse(plainCommandData, errorCode, errorMessage);
if (channel) {
// XXX: get rid of any, same code existed in BidiServer.
this.sendMessage({
...errorResponse,
channel,
});
}
else {
this.sendMessage(errorResponse);
}
}
static #getJsonType(value) {
if (value === null) {
return 'null';
}
if (Array.isArray(value)) {
return 'array';
}
return typeof value;
}
static #getErrorResponse(messageStr, errorCode, errorMessage) {
// XXX: this is bizarre per spec. We reparse the payload and
// extract the ID, regardless of what kind of value it was.
let messageId;
try {
const messageObj = JSON.parse(messageStr);
if (WindowBidiTransport.#getJsonType(messageObj) === 'object' &&
'id' in messageObj) {
messageId = messageObj.id;
}
}
catch { }
return {
type: 'error',
id: messageId,
error: errorCode,
message: errorMessage,
// XXX: optional stacktrace field.
};
}
static #parseBidiMessage(messageStr) {
let messageObject;
try {
messageObject = JSON.parse(messageStr);
}
catch {
throw new Error('Cannot parse data as JSON');
}
const parsedType = WindowBidiTransport.#getJsonType(messageObject);
if (parsedType !== 'object') {
throw new Error(`Expected JSON object but got ${parsedType}`);
}
// Extract and validate id, method and params.
const { id, method, params } = messageObject;
const idType = WindowBidiTransport.#getJsonType(id);
if (idType !== 'number' || !Number.isInteger(id) || id < 0) {
// TODO: should uint64_t be the upper limit?
// https://tools.ietf.org/html/rfc7049#section-2.1
throw new Error(`Expected unsigned integer but got ${idType}`);
}
const methodType = WindowBidiTransport.#getJsonType(method);
if (methodType !== 'string') {
throw new Error(`Expected string method but got ${methodType}`);
}
const paramsType = WindowBidiTransport.#getJsonType(params);
if (paramsType !== 'object') {
throw new Error(`Expected object params but got ${paramsType}`);
}
let channel = messageObject.channel;
if (channel !== undefined) {
const channelType = WindowBidiTransport.#getJsonType(channel);
if (channelType !== 'string') {
throw new Error(`Expected string channel but got ${channelType}`);
}
// Empty string channel is considered as no channel provided.
if (channel === '') {
channel = undefined;
}
}
return { id, method, params, channel };
}
}
return bidiMapper_js_1.BidiServer.createAndStart(new WindowBidiTransport(), createCdpConnection(), selfTargetId, new BidiParserImpl_1.BidiParserImpl(), mapperTabPage_js_1.log);
}
// Needed to filter out info related to BiDi target.
async function waitSelfTargetId() {
return new Promise((resolve) => {
window.setSelfTargetId = (targetId) => {
(0, mapperTabPage_js_1.log)(log_js_1.LogType.system, 'Current target ID:', targetId);
resolve(targetId);
};
});
}
//# sourceMappingURL=bidiTab.js.map