Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

chromium-bidi

Package Overview
Dependencies
Maintainers
2
Versions
73
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

chromium-bidi - npm Package Compare versions

Comparing version 0.5.4 to 0.5.5

lib/cjs/bidiMapper/domains/script/SharedId.d.ts

22

lib/cjs/bidiMapper/BidiServer.js

@@ -46,3 +46,3 @@ "use strict";

};
constructor(bidiTransport, cdpConnection, browserCdpClient, selfTargetId, options, parser, logger) {
constructor(bidiTransport, cdpConnection, browserCdpClient, selfTargetId, defaultUserContextId, options, parser, logger) {
super();

@@ -54,3 +54,3 @@ this.#logger = logger;

this.#eventManager = new EventManager_js_1.EventManager(this.#browsingContextStorage);
this.#commandProcessor = new CommandProcessor_js_1.CommandProcessor(cdpConnection, browserCdpClient, this.#eventManager, selfTargetId, this.#browsingContextStorage, new RealmStorage_js_1.RealmStorage(), options?.acceptInsecureCerts ?? false, options?.sharedIdWithFrame ?? false, parser, this.#logger);
this.#commandProcessor = new CommandProcessor_js_1.CommandProcessor(cdpConnection, browserCdpClient, this.#eventManager, selfTargetId, defaultUserContextId, this.#browsingContextStorage, new RealmStorage_js_1.RealmStorage(), options?.acceptInsecureCerts ?? false, options?.sharedIdWithFrame ?? false, parser, this.#logger);
this.#eventManager.on("event" /* EventManagerEvents.Event */, ({ message, event }) => {

@@ -67,3 +67,19 @@ this.emitOutgoingMessage(message, event);

static async createAndStart(bidiTransport, cdpConnection, browserCdpClient, selfTargetId, options, parser, logger) {
const server = new BidiServer(bidiTransport, cdpConnection, browserCdpClient, selfTargetId, options, parser, logger);
// The default context is not exposed in Target.getBrowserContexts but can
// be observed via Target.getTargets. To determine the default browser
// context, we check which one is mentioned in Target.getTargets and not in
// Target.getBrowserContexts.
const [{ browserContextIds }, { targetInfos }] = await Promise.all([
browserCdpClient.sendCommand('Target.getBrowserContexts'),
browserCdpClient.sendCommand('Target.getTargets'),
]);
let defaultUserContextId = 'default';
for (const info of targetInfos) {
if (info.browserContextId &&
!browserContextIds.includes(info.browserContextId)) {
defaultUserContextId = info.browserContextId;
break;
}
}
const server = new BidiServer(bidiTransport, cdpConnection, browserCdpClient, selfTargetId, defaultUserContextId, options, parser, logger);
// Needed to get events about new targets.

@@ -70,0 +86,0 @@ await browserCdpClient.sendCommand('Target.setDiscoverTargets', {

2

lib/cjs/bidiMapper/CommandProcessor.d.ts

@@ -39,5 +39,5 @@ /**

#private;
constructor(cdpConnection: CdpConnection, browserCdpClient: CdpClient, eventManager: EventManager, selfTargetId: string, browsingContextStorage: BrowsingContextStorage, realmStorage: RealmStorage, acceptInsecureCerts: boolean, sharedIdWithFrame: boolean, parser?: BidiCommandParameterParser, logger?: LoggerFn);
constructor(cdpConnection: CdpConnection, browserCdpClient: CdpClient, eventManager: EventManager, selfTargetId: string, defaultUserContextId: string, browsingContextStorage: BrowsingContextStorage, realmStorage: RealmStorage, acceptInsecureCerts: boolean, sharedIdWithFrame: boolean, parser?: BidiCommandParameterParser, logger?: LoggerFn);
processCommand(command: ChromiumBidi.Command): Promise<void>;
}
export {};

@@ -50,3 +50,3 @@ "use strict";

#logger;
constructor(cdpConnection, browserCdpClient, eventManager, selfTargetId, browsingContextStorage, realmStorage, acceptInsecureCerts, sharedIdWithFrame, parser = new BidiNoOpParser_js_1.BidiNoOpParser(), logger) {
constructor(cdpConnection, browserCdpClient, eventManager, selfTargetId, defaultUserContextId, browsingContextStorage, realmStorage, acceptInsecureCerts, sharedIdWithFrame, parser = new BidiNoOpParser_js_1.BidiNoOpParser(), logger) {
super();

@@ -59,3 +59,3 @@ this.#parser = parser;

this.#browserProcessor = new BrowserProcessor_js_1.BrowserProcessor(browserCdpClient);
this.#browsingContextProcessor = new BrowsingContextProcessor_js_1.BrowsingContextProcessor(cdpConnection, browserCdpClient, selfTargetId, eventManager, browsingContextStorage, realmStorage, networkStorage, preloadScriptStorage, acceptInsecureCerts, sharedIdWithFrame, logger);
this.#browsingContextProcessor = new BrowsingContextProcessor_js_1.BrowsingContextProcessor(cdpConnection, browserCdpClient, selfTargetId, eventManager, browsingContextStorage, realmStorage, networkStorage, preloadScriptStorage, acceptInsecureCerts, sharedIdWithFrame, defaultUserContextId, logger);
this.#cdpProcessor = new CdpProcessor_js_1.CdpProcessor(browsingContextStorage, cdpConnection, browserCdpClient);

@@ -80,2 +80,8 @@ this.#inputProcessor = new InputProcessor_js_1.InputProcessor(browsingContextStorage, realmStorage);

return this.#browserProcessor.close();
case 'browser.createUserContext':
return await this.#browserProcessor.createUserContext();
case 'browser.getUserContexts':
return await this.#browserProcessor.getUserContexts();
case 'browser.removeUserContext':
return await this.#browserProcessor.removeUserContext(command.params.userContext);
// keep-sorted end

@@ -82,0 +88,0 @@ // Browsing Context domain

@@ -17,3 +17,3 @@ /**

*/
import type { EmptyResult } from '../../../protocol/protocol.js';
import { type EmptyResult, type Browser } from '../../../protocol/protocol.js';
import type { CdpClient } from '../../BidiMapper.js';

@@ -24,2 +24,5 @@ export declare class BrowserProcessor {

close(): EmptyResult;
createUserContext(): Promise<Browser.CreateUserContextResult>;
removeUserContext(userContext: Browser.UserContext): Promise<EmptyResult>;
getUserContexts(): Promise<Browser.GetUserContextsResult>;
}

@@ -20,2 +20,3 @@ "use strict";

exports.BrowserProcessor = void 0;
const protocol_js_1 = require("../../../protocol/protocol.js");
class BrowserProcessor {

@@ -32,4 +33,43 @@ #browserCdpClient;

}
async createUserContext() {
const context = await this.#browserCdpClient.sendCommand('Target.createBrowserContext');
return {
userContext: context.browserContextId,
};
}
async removeUserContext(userContext) {
if (userContext === 'default') {
throw new protocol_js_1.InvalidArgumentException('`default` user context cannot be removed');
}
try {
await this.#browserCdpClient.sendCommand('Target.disposeBrowserContext', {
browserContextId: userContext,
});
}
catch (err) {
// https://source.chromium.org/chromium/chromium/src/+/main:content/browser/devtools/protocol/target_handler.cc;l=1424;drc=c686e8f4fd379312469fe018f5c390e9c8f20d0d
if (err.message.startsWith('Failed to find context with id')) {
throw new protocol_js_1.NoSuchUserContextException(err.message);
}
throw err;
}
return {};
}
async getUserContexts() {
const result = await this.#browserCdpClient.sendCommand('Target.getBrowserContexts');
return {
userContexts: [
{
userContext: 'default',
},
...result.browserContextIds.map((id) => {
return {
userContext: id,
};
}),
],
};
}
}
exports.BrowserProcessor = BrowserProcessor;
//# sourceMappingURL=BrowserProcessor.js.map

@@ -28,4 +28,5 @@ /**

static readonly LOGGER_PREFIX: "debug:browsingContext";
readonly userContext: string;
private constructor();
static create(cdpTarget: CdpTarget, realmStorage: RealmStorage, id: BrowsingContext.BrowsingContext, parentId: BrowsingContext.BrowsingContext | null, eventManager: EventManager, browsingContextStorage: BrowsingContextStorage, sharedIdWithFrame: boolean, logger?: LoggerFn): BrowsingContextImpl;
static create(cdpTarget: CdpTarget, realmStorage: RealmStorage, id: BrowsingContext.BrowsingContext, parentId: BrowsingContext.BrowsingContext | null, userContext: string, eventManager: EventManager, browsingContextStorage: BrowsingContextStorage, sharedIdWithFrame: boolean, logger?: LoggerFn): BrowsingContextImpl;
static getTimestamp(): number;

@@ -32,0 +33,0 @@ /**

@@ -30,2 +30,3 @@ "use strict";

#id;
userContext;
/**

@@ -57,3 +58,3 @@ * The ID of the parent browsing context.

#logger;
constructor(cdpTarget, realmStorage, id, parentId, eventManager, browsingContextStorage, sharedIdWithFrame, logger) {
constructor(cdpTarget, realmStorage, id, parentId, userContext, eventManager, browsingContextStorage, sharedIdWithFrame, logger) {
this.#cdpTarget = cdpTarget;

@@ -63,2 +64,3 @@ this.#realmStorage = realmStorage;

this.#parentId = parentId;
this.userContext = userContext;
this.#eventManager = eventManager;

@@ -69,4 +71,4 @@ this.#browsingContextStorage = browsingContextStorage;

}
static create(cdpTarget, realmStorage, id, parentId, eventManager, browsingContextStorage, sharedIdWithFrame, logger) {
const context = new BrowsingContextImpl(cdpTarget, realmStorage, id, parentId, eventManager, browsingContextStorage, sharedIdWithFrame, logger);
static create(cdpTarget, realmStorage, id, parentId, userContext, eventManager, browsingContextStorage, sharedIdWithFrame, logger) {
const context = new BrowsingContextImpl(cdpTarget, realmStorage, id, parentId, userContext, eventManager, browsingContextStorage, sharedIdWithFrame, logger);
context.#initListeners();

@@ -216,2 +218,3 @@ browsingContextStorage.addContext(context);

url: this.url,
userContext: this.userContext,
children: maxDepth > 0

@@ -218,0 +221,0 @@ ? this.directChildren.map((c) => c.serializeToBidiValue(maxDepth - 1, false))

@@ -12,3 +12,3 @@ import type { CdpClient } from '../../../cdp/CdpClient.js';

#private;
constructor(cdpConnection: CdpConnection, browserCdpClient: CdpClient, selfTargetId: string, eventManager: EventManager, browsingContextStorage: BrowsingContextStorage, realmStorage: RealmStorage, networkStorage: NetworkStorage, preloadScriptStorage: PreloadScriptStorage, acceptInsecureCerts: boolean, sharedIdWithFrame: boolean, logger?: LoggerFn);
constructor(cdpConnection: CdpConnection, browserCdpClient: CdpClient, selfTargetId: string, eventManager: EventManager, browsingContextStorage: BrowsingContextStorage, realmStorage: RealmStorage, networkStorage: NetworkStorage, preloadScriptStorage: PreloadScriptStorage, acceptInsecureCerts: boolean, sharedIdWithFrame: boolean, defaultUserContextId: string, logger?: LoggerFn);
getTree(params: BrowsingContext.GetTreeParameters): BrowsingContext.GetTreeResult;

@@ -15,0 +15,0 @@ create(params: BrowsingContext.CreateParameters): Promise<BrowsingContext.CreateResult>;

@@ -20,4 +20,5 @@ "use strict";

#realmStorage;
#defaultUserContextId;
#logger;
constructor(cdpConnection, browserCdpClient, selfTargetId, eventManager, browsingContextStorage, realmStorage, networkStorage, preloadScriptStorage, acceptInsecureCerts, sharedIdWithFrame, logger) {
constructor(cdpConnection, browserCdpClient, selfTargetId, eventManager, browsingContextStorage, realmStorage, networkStorage, preloadScriptStorage, acceptInsecureCerts, sharedIdWithFrame, defaultUserContextId, logger) {
this.#acceptInsecureCerts = acceptInsecureCerts;

@@ -33,2 +34,3 @@ this.#cdpConnection = cdpConnection;

this.#sharedIdWithFrame = sharedIdWithFrame;
this.#defaultUserContextId = defaultUserContextId;
this.#logger = logger;

@@ -47,2 +49,3 @@ this.#setEventListeners(browserCdpClient);

let referenceContext;
let userContext = params.userContext ?? 'default';
if (params.referenceContext !== undefined) {

@@ -53,18 +56,38 @@ referenceContext = this.#browsingContextStorage.getContext(params.referenceContext);

}
userContext = referenceContext.userContext;
}
let result;
let newWindow = false;
switch (params.type) {
case "tab" /* BrowsingContext.CreateType.Tab */:
result = await this.#browserCdpClient.sendCommand('Target.createTarget', {
url: 'about:blank',
newWindow: false,
});
newWindow = false;
break;
case "window" /* BrowsingContext.CreateType.Window */:
result = await this.#browserCdpClient.sendCommand('Target.createTarget', {
url: 'about:blank',
newWindow: true,
});
newWindow = true;
break;
}
if (userContext !== 'default') {
const existingContexts = this.#browsingContextStorage
.getAllContexts()
.filter((context) => context.userContext === userContext);
if (!existingContexts.length) {
// If there are no contexts in the given user context, we need to set
// newWindow to true as newWindow=false will be rejected.
newWindow = true;
}
}
let result;
try {
result = await this.#browserCdpClient.sendCommand('Target.createTarget', {
url: 'about:blank',
newWindow,
browserContextId: userContext === 'default' ? undefined : userContext,
});
}
catch (err) {
// https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/devtools/protocol/target_handler.cc;l=1;drc=e80392ac11e48a691f4309964cab83a3a59e01c8
if (err.message.startsWith('Failed to find browser context with id')) {
throw new protocol_js_1.NoSuchUserContextException(`The context ${userContext} was not found`);
}
throw err;
}
// Wait for the new tab to be loaded to avoid race conditions in the

@@ -187,3 +210,3 @@ // `browsingContext` events, when the `browsingContext.domContentLoaded` and

if (parentBrowsingContext !== undefined) {
BrowsingContextImpl_js_1.BrowsingContextImpl.create(parentBrowsingContext.cdpTarget, this.#realmStorage, params.frameId, params.parentFrameId, this.#eventManager, this.#browsingContextStorage, this.#sharedIdWithFrame, this.#logger);
BrowsingContextImpl_js_1.BrowsingContextImpl.create(parentBrowsingContext.cdpTarget, this.#realmStorage, params.frameId, params.parentFrameId, parentBrowsingContext.userContext, this.#eventManager, this.#browsingContextStorage, this.#sharedIdWithFrame, this.#logger);
}

@@ -217,3 +240,6 @@ }

// New context.
BrowsingContextImpl_js_1.BrowsingContextImpl.create(cdpTarget, this.#realmStorage, targetInfo.targetId, null, this.#eventManager, this.#browsingContextStorage, this.#sharedIdWithFrame, this.#logger);
BrowsingContextImpl_js_1.BrowsingContextImpl.create(cdpTarget, this.#realmStorage, targetInfo.targetId, null, targetInfo.browserContextId &&
targetInfo.browserContextId !== this.#defaultUserContextId
? targetInfo.browserContextId
: 'default', this.#eventManager, this.#browsingContextStorage, this.#sharedIdWithFrame, this.#logger);
}

@@ -220,0 +246,0 @@ return;

@@ -54,9 +54,9 @@ /**

count: number;
"__#88814@#x": number;
"__#88814@#y": number;
"__#88814@#time": number;
"__#88961@#x": number;
"__#88961@#y": number;
"__#88961@#time": number;
compare(context: any): boolean;
};
"__#88814@#DOUBLE_CLICK_TIME_MS": number;
"__#88814@#MAX_DOUBLE_CLICK_RADIUS": number;
"__#88961@#DOUBLE_CLICK_TIME_MS": number;
"__#88961@#MAX_DOUBLE_CLICK_RADIUS": number;
};

@@ -63,0 +63,0 @@ setClickCount(button: number, context: InstanceType<typeof PointerSource.ClickContext>): number;

@@ -8,3 +8,3 @@ "use strict";

const ChannelProxy_js_1 = require("./ChannelProxy.js");
const SharedIdParser_js_1 = require("./SharedIdParser.js");
const SharedId_js_1 = require("./SharedId.js");
class Realm {

@@ -112,3 +112,3 @@ #realmStorage;

deepSerializedValue.sharedId =
SharedIdParser_js_1.SharedIdParser.getSharedId(this.#getBrowsingContextId(navigableId), navigableId, bidiValue.backendNodeId, this.#sharedIdWithFrame);
(0, SharedId_js_1.getSharedId)(this.#getBrowsingContextId(navigableId), navigableId, bidiValue.backendNodeId, this.#sharedIdWithFrame);
delete bidiValue['backendNodeId'];

@@ -351,3 +351,3 @@ }

if ('sharedId' in localValue && localValue.sharedId) {
const parsedSharedId = SharedIdParser_js_1.SharedIdParser.parseSharedId(localValue.sharedId);
const parsedSharedId = (0, SharedId_js_1.parseSharedId)(localValue.sharedId);
if (parsedSharedId === null) {

@@ -354,0 +354,0 @@ throw new protocol_js_1.NoSuchNodeException(`SharedId "${localValue.sharedId}" was not found.`);

@@ -25,4 +25,6 @@ "use strict";

.filter(
// CDP's partition key is the source origin.
(c) => c.partitionKey === undefined ||
// CDP's partition key is the source origin. If the request specifies the
// `sourceOrigin` partition key, only cookies with the requested source origin
// are returned.
(c) => partitionKey.sourceOrigin === undefined ||
c.partitionKey === partitionKey.sourceOrigin)

@@ -54,20 +56,26 @@ .map((c) => this.#cdpToBiDiCookie(c))

const browsingContextId = descriptor.context;
const browsingContext = this.#browsingContextStorage.getContext(browsingContextId);
const url = NetworkProcessor_js_1.NetworkProcessor.parseUrlString(browsingContext?.url ?? '');
// Cookie origin should not contain the port.
// Origin `null` is a special case for local pages.
const sourceOrigin = url.origin === 'null' ? url.origin : `${url.protocol}//${url.hostname}`;
return {
sourceOrigin,
};
// Assert the browsing context exists.
this.#browsingContextStorage.getContext(browsingContextId);
// https://w3c.github.io/webdriver-bidi/#associated-storage-partition.
// Each browsing context also has an associated storage partition, which is the
// storage partition it uses to persist data. In Chromium it's a `BrowserContext`
// which maps to BiDi `UserContext`.
// TODO: extend with UserContext.
return {};
}
#expandStoragePartitionSpecByStorageKey(descriptor) {
let sourceOrigin = undefined;
if (descriptor.sourceOrigin !== undefined) {
sourceOrigin = descriptor.sourceOrigin;
const unsupportedPartitionKeys = new Map();
let sourceOrigin = descriptor.sourceOrigin;
if (sourceOrigin !== undefined) {
const url = NetworkProcessor_js_1.NetworkProcessor.parseUrlString(sourceOrigin);
if (url.origin === 'null') {
// Origin `null` is a special case for local pages.
sourceOrigin = url.origin;
}
else {
// Port is not supported in CDP Cookie's `partitionKey`, so it should be stripped
// from the requested source origin.
sourceOrigin = `${url.protocol}//${url.hostname}`;
}
}
if (sourceOrigin === undefined) {
throw new protocol_js_1.UnderspecifiedStoragePartitionException('"sourceOrigin" should be set');
}
const unsupportedPartitionKeys = new Map();
// Partition spec is a storage partition.

@@ -86,3 +94,3 @@ // Let partition key be partition spec.

return {
sourceOrigin,
...(sourceOrigin === undefined ? {} : { sourceOrigin }),
};

@@ -92,3 +100,3 @@ }

if (partitionSpec === undefined) {
throw new protocol_js_1.UnderspecifiedStoragePartitionException('partition should be set');
return {};
}

@@ -115,3 +123,5 @@ if (partitionSpec.type === 'context') {

// CDP's `partitionKey` is the BiDi's `partition.sourceOrigin`.
partitionKey: partitionKey.sourceOrigin,
...(partitionKey.sourceOrigin !== undefined && {
partitionKey: partitionKey.sourceOrigin,
}),
...(params.cookie.expiry !== undefined && {

@@ -118,0 +128,0 @@ expires: params.cookie.expiry,

@@ -56,3 +56,3 @@ "use strict";

(0, WebSocketServer_js_1.debugInfo)('Launching BiDi server...');
WebSocketServer_js_1.WebSocketServer.run(port, channel, headless, verbose);
new WebSocketServer_js_1.WebSocketServer(port, channel, headless, verbose);
(0, WebSocketServer_js_1.debugInfo)('BiDi server launched');

@@ -59,0 +59,0 @@ }

@@ -41,8 +41,7 @@ "use strict";

#cdpConnection;
#mapperCdpClient;
#bidiSession;
static async create(cdpConnection, mapperTabSource, verbose, mapperOptions) {
try {
const mapperCdpClient = await this.#initMapper(cdpConnection, mapperTabSource, verbose, mapperOptions);
return new MapperServerCdpConnection(cdpConnection, mapperCdpClient);
const bidiSession = await this.#initMapper(cdpConnection, mapperTabSource, verbose, mapperOptions);
return new MapperServerCdpConnection(cdpConnection, bidiSession);
}

@@ -54,14 +53,9 @@ catch (e) {

}
constructor(cdpConnection, mapperCdpClient) {
constructor(cdpConnection, bidiSession) {
this.#cdpConnection = cdpConnection;
this.#mapperCdpClient = mapperCdpClient;
this.#bidiSession = new SimpleTransport_js_1.SimpleTransport(async (message) => await this.#sendMessage(message));
this.#mapperCdpClient.on('Runtime.bindingCalled', this.#onBindingCalled);
this.#mapperCdpClient.on('Runtime.consoleAPICalled', this.#onConsoleAPICalled);
// Catch unhandled exceptions in the mapper.
this.#mapperCdpClient.on('Runtime.exceptionThrown', this.#onRuntimeExceptionThrown);
this.#bidiSession = bidiSession;
}
async #sendMessage(message) {
static async #sendMessage(mapperCdpClient, message) {
try {
await this.#mapperCdpClient.sendCommand('Runtime.evaluate', {
await mapperCdpClient.sendCommand('Runtime.evaluate', {
expression: `onBidiMessage(${JSON.stringify(message)})`,

@@ -80,5 +74,5 @@ });

}
#onBindingCalled = (params) => {
static #onBindingCalled = (params, bidiSession) => {
if (params.name === 'sendBidiResponse') {
this.#bidiSession.emit('message', params.payload);
bidiSession.emit('message', params.payload);
}

@@ -89,3 +83,3 @@ else if (params.name === 'sendDebugMessage') {

};
#onDebugMessage = (json) => {
static #onDebugMessage = (json) => {
try {

@@ -103,6 +97,6 @@ const log = JSON.parse(json);

};
#onConsoleAPICalled = (params) => {
static #onConsoleAPICalled = (params) => {
debugInfo('consoleAPICalled: %s %O', params.type, params.args.map((arg) => arg.value));
};
#onRuntimeExceptionThrown = (params) => {
static #onRuntimeExceptionThrown = (params) => {
debugInfo('exceptionThrown:', params);

@@ -118,2 +112,9 @@ };

const mapperCdpClient = cdpConnection.getCdpClient(mapperSessionId);
const bidiSession = new SimpleTransport_js_1.SimpleTransport(async (message) => await this.#sendMessage(mapperCdpClient, message));
// Process responses from the mapper tab.
mapperCdpClient.on('Runtime.bindingCalled', (params) => this.#onBindingCalled(params, bidiSession));
// Forward console messages from the mapper tab.
mapperCdpClient.on('Runtime.consoleAPICalled', this.#onConsoleAPICalled);
// Catch unhandled exceptions in the mapper.
mapperCdpClient.on('Runtime.exceptionThrown', this.#onRuntimeExceptionThrown);
await mapperCdpClient.sendCommand('Runtime.enable');

@@ -142,3 +143,3 @@ await browserClient.sendCommand('Target.exposeDevToolsProtocol', {

debugInternal('Mapper is launched!');
return mapperCdpClient;
return bidiSession;
}

@@ -145,0 +146,0 @@ }

@@ -6,9 +6,3 @@ import type { ChromeReleaseChannel } from '@puppeteer/browsers';

#private;
/**
* @param bidiPort Port to start ws server on.
* @param channel
* @param headless
* @param verbose
*/
static run(bidiPort: number, channel: ChromeReleaseChannel, headless: boolean, verbose: boolean): void;
constructor(port: number, channel: ChromeReleaseChannel, headless: boolean, verbose: boolean);
}

@@ -56,201 +56,208 @@ "use strict";

class WebSocketServer {
static #sessions = new Map();
/**
* @param bidiPort Port to start ws server on.
* @param channel
* @param headless
* @param verbose
*/
static run(bidiPort, channel, headless, verbose) {
const server = http_1.default.createServer(async (request, response) => {
debugInternal(`${new Date().toString()} Received HTTP ${JSON.stringify(request.method)} request for ${JSON.stringify(request.url)}`);
if (!request.url) {
return response.end(404);
}
// https://w3c.github.io/webdriver-bidi/#transport, step 2.
if (request.url === '/session') {
const body = [];
request
.on('data', (chunk) => {
body.push(chunk);
})
.on('end', () => {
const jsonBody = JSON.parse(Buffer.concat(body).toString());
response.writeHead(200, {
'Content-Type': 'application/json;charset=utf-8',
'Cache-Control': 'no-cache',
});
const sessionId = (0, uuid_js_1.uuidv4)();
const session = {
sessionId,
// TODO: launch browser instance and set it to the session after WPT
// tests clean up is switched to pure BiDi.
browserInstancePromise: undefined,
sessionOptions: {
chromeOptions: this.#getChromeOptions(jsonBody.capabilities, channel, headless),
mapperOptions: this.#getMapperOptions(jsonBody.capabilities),
verbose,
},
};
this.#sessions.set(sessionId, session);
const webSocketUrl = `ws://localhost:${bidiPort}/session/${sessionId}`;
debugInternal(`Session created. WebSocket URL: ${JSON.stringify(webSocketUrl)}.`);
response.write(JSON.stringify({
value: {
sessionId,
capabilities: {
webSocketUrl,
},
},
}));
return response.end();
#sessions = new Map();
#port;
#channel;
#headless;
#verbose;
#server;
#wsServer;
constructor(port, channel, headless, verbose) {
this.#port = port;
this.#channel = channel;
this.#headless = headless;
this.#verbose = verbose;
this.#server = http_1.default.createServer(this.#onRequest.bind(this));
this.#wsServer = new websocket.server({
httpServer: this.#server,
autoAcceptConnections: false,
});
this.#wsServer.on('request', this.#onWsRequest.bind(this));
this.#server.listen(this.#port, () => {
(0, exports.debugInfo)('BiDi server is listening on port', this.#port);
});
}
async #onRequest(request, response) {
debugInternal(`Received HTTP ${JSON.stringify(request.method)} request for ${JSON.stringify(request.url)}`);
if (!request.url) {
return response.end(404);
}
// https://w3c.github.io/webdriver-bidi/#transport, step 2.
if (request.url === '/session') {
const body = await new Promise((resolve, reject) => {
const bodyArray = [];
request.on('data', (chunk) => {
bodyArray.push(chunk);
});
request.on('error', reject);
request.on('end', () => {
resolve(Buffer.concat(bodyArray));
});
});
// https://w3c.github.io/webdriver-bidi/#transport, step 3.
const jsonBody = JSON.parse(body.toString());
response.writeHead(200, {
'Content-Type': 'application/json;charset=utf-8',
'Cache-Control': 'no-cache',
});
const sessionId = (0, uuid_js_1.uuidv4)();
const session = {
sessionId,
// TODO: launch browser instance and set it to the session after WPT
// tests clean up is switched to pure BiDi.
browserInstancePromise: undefined,
sessionOptions: {
chromeOptions: this.#getChromeOptions(jsonBody.capabilities, this.#channel, this.#headless),
mapperOptions: this.#getMapperOptions(jsonBody.capabilities),
verbose: this.#verbose,
},
};
this.#sessions.set(sessionId, session);
const webSocketUrl = `ws://localhost:${this.#port}/session/${sessionId}`;
debugInternal(`Session created. WebSocket URL: ${JSON.stringify(webSocketUrl)}.`);
response.write(JSON.stringify({
value: {
sessionId,
capabilities: {
webSocketUrl,
},
},
}));
return response.end();
}
else if (request.url.startsWith('/session')) {
debugInternal(`Unknown session command ${request.method ?? 'UNKNOWN METHOD'} request for ${request.url} with payload ${await this.#getHttpRequestPayload(request)}. 200 returned.`);
response.writeHead(200, {
'Content-Type': 'application/json;charset=utf-8',
'Cache-Control': 'no-cache',
});
response.write(JSON.stringify({
value: {},
}));
return response.end();
}
debugInternal(`Unknown ${request.method} request for ${JSON.stringify(request.url)} with payload ${await this.#getHttpRequestPayload(request)}. 404 returned.`);
return response.end(404);
}
#onWsRequest(request) {
// Session is set either by Classic or BiDi commands.
let session;
const requestSessionId = (request.resource ?? '').split('/').pop();
debugInternal(`new WS request received. Path: ${JSON.stringify(request.resourceURL.path)}, sessionId: ${JSON.stringify(requestSessionId)}`);
if (requestSessionId !== '' &&
requestSessionId !== undefined &&
!this.#sessions.has(requestSessionId)) {
debugInternal('Unknown session id:', requestSessionId);
request.reject();
return;
}
const connection = request.accept();
session = this.#sessions.get(requestSessionId ?? '');
if (session !== undefined) {
// BrowserInstance is created for each new WS connection, even for the
// same SessionId. This is because WPT uses a single session for all the
// tests, but cleans up tests using WebDriver Classic commands, which is
// not implemented in this Mapper runner.
// TODO: connect to an existing BrowserInstance instead.
const sessionOptions = session.sessionOptions;
session.browserInstancePromise = this.#closeBrowserInstanceIfLaunched(session)
.then(async () => await this.#launchBrowserInstance(connection, sessionOptions))
.catch((e) => {
(0, exports.debugInfo)('Error while creating session', e);
connection.close(500, 'cannot create browser instance');
throw e;
});
}
connection.on('message', async (message) => {
// If type is not text, return error.
if (message.type !== 'utf8') {
this.#respondWithError(connection, {}, "invalid argument" /* ErrorCode.InvalidArgument */, `not supported type (${message.type})`);
return;
}
else if (request.url.startsWith('/session')) {
debugInternal(`Unknown session command ${request.method ?? 'UNKNOWN METHOD'} request for ${request.url} with payload ${await WebSocketServer.#getHttpRequestPayload(request)}. 200 returned.`);
response.writeHead(200, {
'Content-Type': 'application/json;charset=utf-8',
'Cache-Control': 'no-cache',
});
response.write(JSON.stringify({
value: {},
}));
const plainCommandData = message.utf8Data;
if (debugRecv.enabled) {
try {
debugRecv(JSON.parse(plainCommandData));
}
catch {
debugRecv(plainCommandData);
}
}
else {
debugInternal(`Unknown ${JSON.stringify(request.method)} request for ${JSON.stringify(request.url)} with payload ${JSON.stringify(await WebSocketServer.#getHttpRequestPayload(request))}. 404 returned.`);
response.writeHead(404);
// Try to parse the message to handle some of BiDi commands.
let parsedCommandData;
try {
parsedCommandData = JSON.parse(plainCommandData);
}
return response.end();
});
server.listen(bidiPort, () => {
(0, exports.debugInfo)('BiDi server is listening on port', bidiPort);
});
const wsServer = new websocket.server({
httpServer: server,
autoAcceptConnections: false,
});
wsServer.on('request', (request) => {
// Session is set either by Classic or BiDi commands.
let session;
const requestSessionId = (request.resource ?? '').split('/').pop();
debugInternal(`new WS request received. Path: ${JSON.stringify(request.resourceURL.path)}, sessionId: ${JSON.stringify(requestSessionId)}`);
if (requestSessionId !== '' &&
requestSessionId !== undefined &&
!this.#sessions.has(requestSessionId)) {
debugInternal('Unknown session id:', requestSessionId);
request.reject();
catch (e) {
this.#respondWithError(connection, {}, "invalid argument" /* ErrorCode.InvalidArgument */, `Cannot parse data as JSON`);
return;
}
const connection = request.accept();
session = this.#sessions.get(requestSessionId ?? '');
if (session !== undefined) {
// BrowserInstance is created for each new WS connection, even for the
// same SessionId. This is because WPT uses a single session for all the
// tests, but cleans up tests using WebDriver Classic commands, which is
// not implemented in this Mapper runner.
// TODO: connect to an existing BrowserInstance instead.
const sessionOptions = session.sessionOptions;
session.browserInstancePromise = this.#closeBrowserInstanceIfLaunched(session)
.then(async () => await this.#launchBrowserInstance(connection, sessionOptions))
.catch((e) => {
(0, exports.debugInfo)('Error while creating session', e);
connection.close(500, 'cannot create browser instance');
throw e;
});
}
connection.on('message', async (message) => {
// If type is not text, return error.
if (message.type !== 'utf8') {
this.#respondWithError(connection, {}, "invalid argument" /* ErrorCode.InvalidArgument */, `not supported type (${message.type})`);
// Handle creating new session.
if (parsedCommandData.method === 'session.new') {
if (session !== undefined) {
(0, exports.debugInfo)('WS connection already have an associated session.');
this.#respondWithError(connection, plainCommandData, "session not created" /* ErrorCode.SessionNotCreated */, 'WS connection already have an associated session.');
return;
}
const plainCommandData = message.utf8Data;
if (debugRecv.enabled) {
try {
debugRecv(JSON.parse(plainCommandData));
}
catch {
debugRecv(plainCommandData);
}
}
// Try to parse the message to handle some of BiDi commands.
let parsedCommandData;
try {
parsedCommandData = JSON.parse(plainCommandData);
const sessionOptions = {
chromeOptions: this.#getChromeOptions(parsedCommandData.params?.capabilities, this.#channel, this.#headless),
mapperOptions: this.#getMapperOptions(parsedCommandData.params?.capabilities),
verbose: this.#verbose,
};
const browserInstance = await this.#launchBrowserInstance(connection, sessionOptions);
const sessionId = (0, uuid_js_1.uuidv4)();
session = {
sessionId,
browserInstancePromise: Promise.resolve(browserInstance),
sessionOptions,
};
this.#sessions.set(sessionId, session);
}
catch (e) {
this.#respondWithError(connection, {}, "invalid argument" /* ErrorCode.InvalidArgument */, `Cannot parse data as JSON`);
(0, exports.debugInfo)('Error while creating session', e);
this.#respondWithError(connection, plainCommandData, "session not created" /* ErrorCode.SessionNotCreated */, e?.message ?? 'Unknown error');
return;
}
// Handle creating new session.
if (parsedCommandData.method === 'session.new') {
if (session !== undefined) {
(0, exports.debugInfo)('WS connection already have an associated session.');
this.#respondWithError(connection, plainCommandData, "session not created" /* ErrorCode.SessionNotCreated */, 'WS connection already have an associated session.');
return;
}
try {
const sessionOptions = {
chromeOptions: this.#getChromeOptions(parsedCommandData.params?.capabilities, channel, headless),
mapperOptions: this.#getMapperOptions(parsedCommandData.params?.capabilities),
verbose,
};
const browserInstance = await this.#launchBrowserInstance(connection, sessionOptions);
const sessionId = (0, uuid_js_1.uuidv4)();
session = {
sessionId,
browserInstancePromise: Promise.resolve(browserInstance),
sessionOptions,
};
this.#sessions.set(sessionId, session);
}
catch (e) {
(0, exports.debugInfo)('Error while creating session', e);
this.#respondWithError(connection, plainCommandData, "session not created" /* ErrorCode.SessionNotCreated */, e?.message ?? 'Unknown error');
return;
}
// TODO: extend with capabilities.
this.#sendClientMessage({
id: parsedCommandData.id,
type: 'success',
result: {
sessionId: session.sessionId,
capabilities: {},
},
}, connection);
return;
}
if (session === undefined) {
(0, exports.debugInfo)('Session is not yet initialized.');
this.#respondWithError(connection, plainCommandData, "invalid session id" /* ErrorCode.InvalidSessionId */, 'Session is not yet initialized.');
return;
}
if (session.browserInstancePromise === undefined) {
(0, exports.debugInfo)('Browser instance is not launched.');
this.#respondWithError(connection, plainCommandData, "invalid session id" /* ErrorCode.InvalidSessionId */, 'Browser instance is not launched.');
return;
}
const browserInstance = await session.browserInstancePromise;
// Handle `browser.close` command.
if (parsedCommandData.method === 'browser.close') {
await browserInstance.close();
this.#sendClientMessage({
id: parsedCommandData.id,
type: 'success',
result: {},
}, connection);
return;
}
// Forward all other commands to BiDi Mapper.
await browserInstance.bidiSession().sendCommand(plainCommandData);
});
connection.on('close', async () => {
debugInternal(`${new Date().toString()} Peer ${connection.remoteAddress} disconnected.`);
// TODO: don't close Browser instance to allow re-connecting to the session.
await this.#closeBrowserInstanceIfLaunched(session);
});
// TODO: extend with capabilities.
this.#sendClientMessage({
id: parsedCommandData.id,
type: 'success',
result: {
sessionId: session.sessionId,
capabilities: {},
},
}, connection);
return;
}
if (session === undefined) {
(0, exports.debugInfo)('Session is not yet initialized.');
this.#respondWithError(connection, plainCommandData, "invalid session id" /* ErrorCode.InvalidSessionId */, 'Session is not yet initialized.');
return;
}
if (session.browserInstancePromise === undefined) {
(0, exports.debugInfo)('Browser instance is not launched.');
this.#respondWithError(connection, plainCommandData, "invalid session id" /* ErrorCode.InvalidSessionId */, 'Browser instance is not launched.');
return;
}
const browserInstance = await session.browserInstancePromise;
// Handle `browser.close` command.
if (parsedCommandData.method === 'browser.close') {
await browserInstance.close();
this.#sendClientMessage({
id: parsedCommandData.id,
type: 'success',
result: {},
}, connection);
return;
}
// Forward all other commands to BiDi Mapper.
await browserInstance.bidiSession().sendCommand(plainCommandData);
});
connection.on('close', async () => {
debugInternal(`Peer ${connection.remoteAddress} disconnected.`);
// TODO: don't close Browser instance to allow re-connecting to the session.
await this.#closeBrowserInstanceIfLaunched(session);
});
}
static async #closeBrowserInstanceIfLaunched(session) {
async #closeBrowserInstanceIfLaunched(session) {
if (session === undefined || session.browserInstancePromise === undefined) {

@@ -263,3 +270,3 @@ return;

}
static #getMapperOptions(capabilities) {
#getMapperOptions(capabilities) {
const acceptInsecureCerts = capabilities?.alwaysMatch?.acceptInsecureCerts ?? false;

@@ -269,3 +276,3 @@ const sharedIdWithFrame = capabilities?.alwaysMatch?.sharedIdWithFrame ?? false;

}
static #getChromeOptions(capabilities, channel, headless) {
#getChromeOptions(capabilities, channel, headless) {
const chromeCapabilities = capabilities?.alwaysMatch?.['goog:chromeOptions'];

@@ -279,3 +286,3 @@ return {

}
static async #launchBrowserInstance(connection, sessionOptions) {
async #launchBrowserInstance(connection, sessionOptions) {
(0, exports.debugInfo)('Scheduling browser launch...');

@@ -290,3 +297,3 @@ const browserInstance = await BrowserInstance_js_1.BrowserInstance.run(sessionOptions.chromeOptions, sessionOptions.mapperOptions, sessionOptions.verbose);

}
static #sendClientMessageString(message, connection) {
#sendClientMessageString(message, connection) {
if (debugSend.enabled) {

@@ -302,11 +309,11 @@ try {

}
static #sendClientMessage(object, connection) {
#sendClientMessage(object, connection) {
const json = JSON.stringify(object);
return this.#sendClientMessageString(json, connection);
}
static #respondWithError(connection, plainCommandData, errorCode, errorMessage) {
#respondWithError(connection, plainCommandData, errorCode, errorMessage) {
const errorResponse = this.#getErrorResponse(plainCommandData, errorCode, errorMessage);
void this.#sendClientMessage(errorResponse, connection);
}
static #getErrorResponse(plainCommandData, errorCode, errorMessage) {
#getErrorResponse(plainCommandData, errorCode, errorMessage) {
// XXX: this is bizarre per spec. We reparse the payload and

@@ -330,3 +337,3 @@ // extract the ID, regardless of what kind of value it was.

}
static #getHttpRequestPayload(request) {
#getHttpRequestPayload(request) {
return new Promise((resolve, reject) => {

@@ -333,0 +340,0 @@ let data = '';

@@ -62,2 +62,5 @@ /**

}
export declare class NoSuchUserContextException extends Exception {
constructor(message: string, stacktrace?: string);
}
export declare class SessionNotCreatedException extends Exception {

@@ -64,0 +67,0 @@ constructor(message: string, stacktrace?: string);

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.UnderspecifiedStoragePartitionException = exports.UnableToSetFileInputException = exports.UnableToSetCookieException = exports.NoSuchStoragePartitionException = exports.UnsupportedOperationException = exports.UnableToCloseBrowserException = exports.UnableToCaptureScreenException = exports.UnknownErrorException = exports.UnknownCommandException = exports.SessionNotCreatedException = exports.NoSuchScriptException = exports.NoSuchRequestException = exports.NoSuchNodeException = exports.NoSuchInterceptException = exports.NoSuchHistoryEntryException = exports.NoSuchHandleException = exports.NoSuchFrameException = exports.NoSuchElementException = exports.NoSuchAlertException = exports.MoveTargetOutOfBoundsException = exports.InvalidSessionIdException = exports.InvalidArgumentException = exports.Exception = void 0;
exports.UnderspecifiedStoragePartitionException = exports.UnableToSetFileInputException = exports.UnableToSetCookieException = exports.NoSuchStoragePartitionException = exports.UnsupportedOperationException = exports.UnableToCloseBrowserException = exports.UnableToCaptureScreenException = exports.UnknownErrorException = exports.UnknownCommandException = exports.SessionNotCreatedException = exports.NoSuchUserContextException = exports.NoSuchScriptException = exports.NoSuchRequestException = exports.NoSuchNodeException = exports.NoSuchInterceptException = exports.NoSuchHistoryEntryException = exports.NoSuchHandleException = exports.NoSuchFrameException = exports.NoSuchElementException = exports.NoSuchAlertException = exports.MoveTargetOutOfBoundsException = exports.InvalidSessionIdException = exports.InvalidArgumentException = exports.Exception = void 0;
class Exception {

@@ -96,2 +96,8 @@ error;

exports.NoSuchScriptException = NoSuchScriptException;
class NoSuchUserContextException extends Exception {
constructor(message, stacktrace) {
super("no such user context" /* ErrorCode.NoSuchUserContext */, message, stacktrace);
}
}
exports.NoSuchUserContextException = NoSuchUserContextException;
class SessionNotCreatedException extends Exception {

@@ -98,0 +104,0 @@ constructor(message, stacktrace) {

@@ -70,2 +70,3 @@ /**

NoSuchStoragePartition = "no such storage partition",
NoSuchUserContext = "no such user context",
SessionNotCreated = "session not created",

@@ -203,4 +204,13 @@ UnableToCaptureScreen = "unable to capture screen",

}
export type BrowserCommand = Browser.Close;
export type BrowserCommand = Browser.Close | Browser.CreateUserContext | Browser.GetUserContexts | Browser.RemoveUserContext;
export type BrowserResult = Browser.CreateUserContextResult | Browser.GetUserContextsResult;
export declare namespace Browser {
type UserContext = string;
}
export declare namespace Browser {
type UserContextInfo = {
userContext: Browser.UserContext;
};
}
export declare namespace Browser {
type Close = {

@@ -211,2 +221,30 @@ method: 'browser.close';

}
export declare namespace Browser {
type CreateUserContext = {
method: 'browser.createUserContext';
params: EmptyParams;
};
}
export declare namespace Browser {
type CreateUserContextResult = Browser.UserContextInfo;
}
export declare namespace Browser {
type GetUserContexts = {
method: 'browser.getUserContexts';
params: EmptyParams;
};
}
export declare namespace Browser {
type GetUserContextsResult = {
userContexts: [Browser.UserContextInfo, ...Browser.UserContextInfo[]];
};
}
export declare namespace Browser {
type RemoveUserContext = {
method: 'browser.removeUserContext';
params: {
userContext: Browser.UserContext;
};
};
}
export type BrowsingContextCommand = BrowsingContext.Activate | BrowsingContext.CaptureScreenshot | BrowsingContext.Close | BrowsingContext.Create | BrowsingContext.GetTree | BrowsingContext.HandleUserPrompt | BrowsingContext.LocateNodes | BrowsingContext.Navigate | BrowsingContext.Print | BrowsingContext.Reload | BrowsingContext.SetViewport | BrowsingContext.TraverseHistory;

@@ -223,5 +261,6 @@ export type BrowsingContextEvent = BrowsingContext.ContextCreated | BrowsingContext.ContextDestroyed | BrowsingContext.DomContentLoaded | BrowsingContext.DownloadWillBegin | BrowsingContext.FragmentNavigated | BrowsingContext.Load | BrowsingContext.NavigationAborted | BrowsingContext.NavigationFailed | BrowsingContext.NavigationStarted | BrowsingContext.UserPromptClosed | BrowsingContext.UserPromptOpened;

type Info = {
children: BrowsingContext.InfoList | null;
context: BrowsingContext.BrowsingContext;
url: string;
children: BrowsingContext.InfoList | null;
userContext: Browser.UserContext;
parent?: BrowsingContext.BrowsingContext | null;

@@ -367,2 +406,3 @@ };

background?: boolean;
userContext?: Browser.UserContext | null;
};

@@ -369,0 +409,0 @@ }

{
"name": "chromium-bidi",
"version": "0.5.4",
"version": "0.5.5",
"description": "An implementation of the WebDriver BiDi protocol for Chromium implemented as a JavaScript layer translating between BiDi and CDP, running inside a Chrome tab.",

@@ -171,6 +171,7 @@ "scripts": {

"@actions/core": "1.10.1",
"@puppeteer/browsers": "1.9.0",
"@puppeteer/browsers": "1.9.1",
"@rollup/plugin-commonjs": "25.0.7",
"@rollup/plugin-node-resolve": "15.2.3",
"@rollup/plugin-terser": "0.4.4",
"@rollup/wasm-node": "4.9.6",
"@types/argparse": "2.0.14",

@@ -182,8 +183,8 @@ "@types/chai": "4.3.11",

"@types/node": "20.10.6",
"@types/sinon": "17.0.2",
"@types/sinon": "17.0.3",
"@types/websocket": "1.0.10",
"@types/ws": "8.5.10",
"@types/yargs": "17.0.32",
"@typescript-eslint/eslint-plugin": "6.17.0",
"@typescript-eslint/parser": "6.17.0",
"@typescript-eslint/eslint-plugin": "6.19.1",
"@typescript-eslint/parser": "6.19.1",
"argparse": "2.0.1",

@@ -199,3 +200,3 @@ "chai": "4.3.10",

"eslint-plugin-mocha": "10.2.0",
"eslint-plugin-prettier": "5.1.2",
"eslint-plugin-prettier": "5.1.3",
"eslint-plugin-promise": "6.1.1",

@@ -206,14 +207,14 @@ "gts": "5.2.0",

"pkg-dir": "8.0.0",
"prettier": "3.1.1",
"prettier": "3.2.4",
"rimraf": "5.0.5",
"rollup": "3.29.4",
"selenium-webdriver": "4.16.0",
"rollup": "4.9.6",
"selenium-webdriver": "4.17.0",
"sinon": "17.0.1",
"source-map-support": "0.5.21",
"terser": "5.26.0",
"terser": "5.27.0",
"tslib": "2.6.2",
"typescript": "5.3.3",
"webdriverio": "8.27.0",
"webdriverio": "8.29.1",
"websocket": "1.0.34",
"wireit": "0.14.1",
"wireit": "0.14.3",
"ws": "8.16.0",

@@ -220,0 +221,0 @@ "yargs": "17.7.2",

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc