Skip to content

Commit

Permalink
fix: rpc protocol devtools capturer (#3558)
Browse files Browse the repository at this point in the history
  • Loading branch information
bytemain authored Apr 23, 2024
1 parent 7e5c4c2 commit 5d54570
Show file tree
Hide file tree
Showing 27 changed files with 463 additions and 261 deletions.
44 changes: 24 additions & 20 deletions packages/addons/src/browser/chrome-devtools.contribution.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,12 @@
import { Autowired } from '@opensumi/di';
import { AppConfig, ClientAppContribution } from '@opensumi/ide-core-browser';
import { AppConfig, ClientAppContribution, Disposable } from '@opensumi/ide-core-browser';
import { DevtoolsLantencyCommand, EDevtoolsEvent } from '@opensumi/ide-core-common/lib/devtools';
import { Domain } from '@opensumi/ide-core-common/lib/di-helper';

import { ConnectionRTTBrowserService, ConnectionRTTBrowserServiceToken } from './connection-rtt-service';

enum DevtoolsEvent {
Latency = 'devtools:latency',
}

enum DevtoolsCommand {
Start = 'start',
Stop = 'stop',
}

@Domain(ClientAppContribution)
export class ChromeDevtoolsContribution implements ClientAppContribution {
export class ChromeDevtoolsContribution extends Disposable implements ClientAppContribution {
@Autowired(AppConfig)
private readonly appConfig: AppConfig;

Expand All @@ -25,23 +17,35 @@ export class ChromeDevtoolsContribution implements ClientAppContribution {

static INTERVAL = 1000;

protected lantencyHandler = (event: CustomEvent) => {
const { command } = event.detail;
if (command === DevtoolsLantencyCommand.Start) {
if (!this.interval) {
this.startRTTInterval();
}
} else if (command === DevtoolsLantencyCommand.Stop) {
if (this.interval) {
global.clearInterval(this.interval);
this.interval = undefined;
}
}
};

initialize() {
// only runs when devtools supoprt is enabled
if (this.appConfig.devtools) {
// receive notification from opensumi devtools by custom event
window.addEventListener(DevtoolsEvent.Latency, (event) => {
const { command } = event.detail;
if (command === DevtoolsCommand.Start) {
if (!this.interval) {
this.startRTTInterval();
}
} else if (command === DevtoolsCommand.Stop) {
window.addEventListener(EDevtoolsEvent.Latency, this.lantencyHandler);

this.addDispose(
Disposable.create(() => {
window.removeEventListener(EDevtoolsEvent.Latency, this.lantencyHandler);
if (this.interval) {
global.clearInterval(this.interval);
this.interval = undefined;
}
}
});
}),
);

// if opensumi devtools has started capturing before this contribution point is registered
if (window.__OPENSUMI_DEVTOOLS_GLOBAL_HOOK__?.captureRPC) {
Expand Down
3 changes: 2 additions & 1 deletion packages/connection/__test__/common/rpc/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import { MessageChannel, MessagePort } from 'worker_threads';

import { Type } from '@furyjs/fury';

import { ProxyJson, WSChannel, createWebSocketConnection } from '@opensumi/ide-connection';
import { ProxyJson, WSChannel } from '@opensumi/ide-connection';
import { NetSocketConnection } from '@opensumi/ide-connection/lib/common/connection';
import { createWebSocketConnection } from '@opensumi/ide-connection/lib/common/message';
import { Deferred } from '@opensumi/ide-core-common';
import { normalizedIpcHandlerPathAsync } from '@opensumi/ide-core-common/lib/utils/ipc';
import { MessageConnection } from '@opensumi/vscode-jsonrpc';
Expand Down
172 changes: 169 additions & 3 deletions packages/connection/src/common/capturer.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
import {
DisposableStore,
IDisposable,
isUint8Array,
randomString,
transformErrorForSerialization,
} from '@opensumi/ide-core-common';
import { DevtoolsLantencyCommand, EDevtoolsEvent } from '@opensumi/ide-core-common/lib/devtools';

declare global {
interface Window {
__OPENSUMI_DEVTOOLS_GLOBAL_HOOK__: any;
Expand All @@ -22,15 +31,172 @@ export interface ICapturedMessage {
type: MessageType;
serviceMethod: string;
arguments?: any;
requestId?: string;
requestId?: string | number;
status?: ResponseStatus;
data?: any;
error?: any;

source?: string;
}

const _global = (typeof window !== 'undefined' ? window : global) || {
__OPENSUMI_DEVTOOLS_GLOBAL_HOOK__: undefined,
};

export function getCapturer() {
if (typeof window !== 'undefined' && window.__OPENSUMI_DEVTOOLS_GLOBAL_HOOK__?.captureRPC) {
return window.__OPENSUMI_DEVTOOLS_GLOBAL_HOOK__.captureRPC;
const hook = _global.__OPENSUMI_DEVTOOLS_GLOBAL_HOOK__;
if (hook) {
return hook.captureRPC;
}
return;
}

export class Capturer implements IDisposable {
protected _disposables = new DisposableStore();

protected capturer: ((data: any) => void) | null = null;
protected prefix: string;

protected setupListener = (event: CustomEvent) => {
const { command } = event.detail;
if (command === DevtoolsLantencyCommand.Start) {
this.capturer = getCapturer();
} else if (command === DevtoolsLantencyCommand.Stop) {
this.capturer = null;
}
};

constructor(protected source: string) {
this.prefix = randomString(6);
this.capturer = getCapturer();

// capturer should only be used in browser environment
if (typeof _global.addEventListener === 'function') {
_global.addEventListener(EDevtoolsEvent.Latency, this.setupListener);
this._disposables.add({
dispose: () => {
_global.removeEventListener(EDevtoolsEvent.Latency, this.setupListener);
},
});
}
}

capture(message: ICapturedMessage): void {
if (!this.capturer) {
return;
}

const data: ICapturedMessage = {
...message,
source: this.source,
};

if (data.data) {
if (isUint8Array(data.data)) {
data.data = '<Uint8Array>';
}
}

if (message.requestId) {
data.requestId = `${this.prefix}-${message.requestId}`;
}

if (message.error) {
data.error = transformErrorForSerialization(message.error);
}

this.capturer(data);
}

captureOnRequest(requestId: ICapturedMessage['requestId'], serviceMethod: string, args: any[]): void {
if (!this.capturer) {
return;
}

this.capture({ type: MessageType.OnRequest, requestId: `↓${requestId}`, serviceMethod, arguments: args });
}

captureOnRequestResult(requestId: ICapturedMessage['requestId'], serviceMethod: string, data: any): void {
if (!this.capturer) {
return;
}

this.capture({
type: MessageType.OnRequestResult,
status: ResponseStatus.Success,
requestId: `↓${requestId}`,
serviceMethod,
data,
});
}

captureOnRequestFail(requestId: ICapturedMessage['requestId'], serviceMethod: string, error: any): void {
if (!this.capturer) {
return;
}

this.capture({
type: MessageType.OnRequestResult,
status: ResponseStatus.Fail,
requestId: `↓${requestId}`,
serviceMethod,
error,
});
}

captureSendRequest(requestId: ICapturedMessage['requestId'] | number, serviceMethod: string, args: any[]): void {
if (!this.capturer) {
return;
}

this.capture({ type: MessageType.SendRequest, requestId, serviceMethod, arguments: args });
}

captureSendRequestResult(requestId: ICapturedMessage['requestId'], serviceMethod: string, data: any): void {
if (!this.capturer) {
return;
}

this.capture({
type: MessageType.RequestResult,
status: ResponseStatus.Success,
requestId,
serviceMethod,
data,
});
}

captureSendRequestFail(requestId: ICapturedMessage['requestId'], serviceMethod: string, error: any): void {
if (!this.capturer) {
return;
}

this.capture({
type: MessageType.RequestResult,
status: ResponseStatus.Fail,
requestId,
serviceMethod,
error,
});
}

captureSendNotification(requestId: ICapturedMessage['requestId'], serviceMethod: string, args: any[]): void {
if (!this.capturer) {
return;
}

this.capture({ type: MessageType.SendNotification, serviceMethod, arguments: args, requestId });
}

captureOnNotification(requestId: ICapturedMessage['requestId'], serviceMethod: string, args: any[]): void {
if (!this.capturer) {
return;
}

this.capture({ type: MessageType.OnNotification, serviceMethod, arguments: args, requestId: `↓${requestId}` });
}

dispose(): void {
this._disposables.dispose();
}
}
1 change: 0 additions & 1 deletion packages/connection/src/common/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
export * from './message';
export * from './rpc-service/proxy';
export * from './rpc/multiplexer';
export * from './rpcProtocol';
Expand Down
3 changes: 2 additions & 1 deletion packages/connection/src/common/rpc-service/center.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Deferred } from '@opensumi/ide-core-common';
import { MessageConnection } from '@opensumi/vscode-jsonrpc';

import { METHOD_NOT_REGISTERED } from '../constants';
import { TSumiProtocol } from '../rpc';
Expand All @@ -10,6 +9,8 @@ import { ProxyJson, ProxySumi } from './proxy';
import { ProxyBase } from './proxy/base';
import { ProtocolRegistry, ServiceRegistry } from './registry';

import type { MessageConnection } from '@opensumi/vscode-jsonrpc';

const safeProcess: { pid: string } = typeof process === 'undefined' ? { pid: 'unknown' } : (process as any);

export class RPCServiceCenter {
Expand Down
Loading

1 comment on commit 5d54570

@opensumi
Copy link
Contributor

@opensumi opensumi bot commented on 5d54570 Apr 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Release Candidate Summary:

Released 🚀 2.27.3-rc-1713855854.0

2.27.3-rc-1713855854.0

user input ref: main

5d54570 fix: rpc protocol devtools capturer (#3558)

Please sign in to comment.