Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: a global hook for OpenSumi DevTools to communicate with #1560

Merged
merged 27 commits into from
Aug 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
32f609e
feat: 捕获MessageConnection的消息
tyn1998 Jun 27, 2022
b226cea
feat: 加载opensumi-devtools插件
tyn1998 Jun 27, 2022
46dab65
chore: onRequest和onNotification的应拦截发生在注册的handler中
tyn1998 Jun 28, 2022
78a5f54
refactor: 配合opensumi-devtools重命名一波
tyn1998 Jul 9, 2022
ab054dc
refactor: 只在common/proxy.ts注射capturer逻辑
tyn1998 Jul 9, 2022
67d45e9
feat: add requestId to associate request and requestResult
tyn1998 Jul 9, 2022
1137263
refactor: new message body when capturing
tyn1998 Jul 26, 2022
b8ddfca
fix: typo
tyn1998 Jul 30, 2022
43193e8
feat: capture onRequestResult
tyn1998 Jul 30, 2022
9ead963
Merge branch 'main' into feat/message-capturer
tyn1998 Aug 1, 2022
2ba2cf6
Merge branch 'main' into feat/message-capturer
tyn1998 Aug 18, 2022
1ed29f1
refactor: join capturing code into one line
tyn1998 Aug 18, 2022
6bc4161
refactor: capture serviceMethod property for all messages
tyn1998 Aug 18, 2022
f4fe5bd
feat: a contribution for opensumi devtools to measure network latency
tyn1998 Aug 18, 2022
26051e5
feat: expose a global to communicate with opensumi devtools
tyn1998 Aug 20, 2022
9f895f2
build: global.d.ts not work
tyn1998 Aug 22, 2022
d98bb78
fix: global.d.ts syntax error
tyn1998 Aug 22, 2022
223f3af
Merge branch 'main' into feat/for-asoc-chrome-devtools
tyn1998 Aug 22, 2022
437c1d4
chore: remove loadExtension code from electron
tyn1998 Aug 22, 2022
0fe0cae
fix: declare in global.d.ts will not work
tyn1998 Aug 22, 2022
aab407a
fix: devtools global hook actually not defined when in test
tyn1998 Aug 22, 2022
07d6fef
refactor: messages capture code
tyn1998 Aug 24, 2022
8e8ca96
refactor: types support
tyn1998 Aug 24, 2022
3b99bcc
refactor: receive notification from opensumi devtools by custom event
tyn1998 Aug 24, 2022
245af37
refactor: substitute generateUniqueIda() with already available uuid()
tyn1998 Aug 24, 2022
416032b
refactor: shorter code with same function
tyn1998 Aug 24, 2022
29ff670
refactor: use enum to define all events and commands from devtools
tyn1998 Aug 24, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions packages/addons/src/browser/chrome-devtools.contribution.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { Autowired } from '@opensumi/di';
import { ClientAppContribution } from '@opensumi/ide-core-browser';
import { Domain } from '@opensumi/ide-core-common/lib/di-helper';

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

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

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

@Domain(ClientAppContribution)
export class ChromeDevtoolsContribution implements ClientAppContribution {
@Autowired(ConnectionRTTBrowserServiceToken)
protected readonly rttService: ConnectionRTTBrowserService;

private interval?: NodeJS.Timeout;

static INTERVAL = 1000;

initialize() {
// 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) {
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__?.capture) {
if (!this.interval) {
this.startRTTInterval();
}
}
}

private startRTTInterval() {
this.interval = global.setInterval(async () => {
const start = Date.now();
await this.rttService.measure();
const rtt = Date.now() - start;
// "if" below is to prevent setting latency after stoping capturing
if (window.__OPENSUMI_DEVTOOLS_GLOBAL_HOOK__.capture) {
window.__OPENSUMI_DEVTOOLS_GLOBAL_HOOK__.latency = rtt;
}
}, ChromeDevtoolsContribution.INTERVAL);
}
}
16 changes: 2 additions & 14 deletions packages/addons/src/browser/connection-rtt-contribution.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { Autowired, Injectable } from '@opensumi/di';
import { Autowired } from '@opensumi/di';
import { IStatusBarService, StatusBarAlignment, StatusBarEntryAccessor } from '@opensumi/ide-core-browser/lib/services';
import { Command, CommandContribution, CommandRegistry } from '@opensumi/ide-core-common/lib/command';
import { Domain } from '@opensumi/ide-core-common/lib/di-helper';
import { localize } from '@opensumi/ide-core-common/lib/localize';

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

const START_CONNECTION_RTT_COMMAND: Command = {
id: 'connection.start.rtt',
Expand All @@ -23,18 +23,6 @@ const statusBarOption = {
priority: Infinity - 1,
};

export const ConnectionRTTBrowserServiceToken = Symbol('ConnectionRTTBrowserService');

@Injectable()
export class ConnectionRTTBrowserService {
@Autowired(ConnectionBackServicePath)
protected readonly connectionBackService: IConnectionBackService;

async measure() {
await this.connectionBackService.$measure();
}
}

@Domain(CommandContribution)
export class ConnectionRTTContribution implements CommandContribution {
@Autowired(IStatusBarService)
Expand Down
15 changes: 15 additions & 0 deletions packages/addons/src/browser/connection-rtt-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Autowired, Injectable } from '@opensumi/di';

import { ConnectionBackServicePath, IConnectionBackService } from '../common';

export const ConnectionRTTBrowserServiceToken = Symbol('ConnectionRTTBrowserService');

@Injectable()
export class ConnectionRTTBrowserService {
@Autowired(ConnectionBackServicePath)
protected readonly connectionBackService: IConnectionBackService;

async measure() {
await this.connectionBackService.$measure();
}
}
9 changes: 4 additions & 5 deletions packages/addons/src/browser/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,9 @@ import { BrowserModule } from '@opensumi/ide-core-browser';

import { IFileDropFrontendServiceToken, FileDropServicePath, ConnectionBackServicePath } from '../common';

import {
ConnectionRTTBrowserService,
ConnectionRTTBrowserServiceToken,
ConnectionRTTContribution,
} from './connection-rtt-contribution';
import { ChromeDevtoolsContribution } from './chrome-devtools.contribution';
import { ConnectionRTTContribution } from './connection-rtt-contribution';
import { ConnectionRTTBrowserService, ConnectionRTTBrowserServiceToken } from './connection-rtt-service';
import { FileDropContribution } from './file-drop.contribution';
import { FileDropService } from './file-drop.service';
import { FileSearchContribution } from './file-search.contribution';
Expand All @@ -18,6 +16,7 @@ import { ToolbarCustomizeContribution } from './toolbar-customize/toolbar-custom
@Injectable()
export class ClientAddonModule extends BrowserModule {
providers = [
ChromeDevtoolsContribution,
LanguageChangeHintContribution,
FileSearchContribution,
StatusBarContribution,
Expand Down
77 changes: 73 additions & 4 deletions packages/connection/src/common/proxy.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { ApplicationError } from '@opensumi/ide-core-common';
import { ApplicationError, uuid } from '@opensumi/ide-core-common';
import type { MessageConnection } from '@opensumi/vscode-jsonrpc/lib/common/connection';

import { MessageType, ResponseStatus, ICapturedMessage, getCapturer } from './utils';

export abstract class RPCService<T = any> {
rpcClient?: T[];
rpcRegistered?: boolean;
Expand Down Expand Up @@ -46,6 +48,13 @@ export class RPCProxy {
private connection: MessageConnection;
private proxyService: any = {};
private logger: any;
// capture messages for opensumi devtools
private capture(message: ICapturedMessage): void {
const capturer = getCapturer();
if (capturer !== undefined) {
capturer(message);
}
}

constructor(public target?: RPCService, logger?: any) {
this.waitForConnection();
Expand Down Expand Up @@ -98,17 +107,24 @@ export class RPCProxy {
if (prop.startsWith('on')) {
if (isSingleArray) {
connection.sendNotification(prop, [...args]);
this.capture({ type: MessageType.SendNotification, serviceMethod: prop, arguments: args });
} else {
connection.sendNotification(prop, ...args);
this.capture({ type: MessageType.SendNotification, serviceMethod: prop, arguments: args });
}

resolve(null);
} else {
let requestResult: Promise<any>;
// generate a unique requestId to associate request and requestResult
const requestId = uuid();

if (isSingleArray) {
requestResult = connection.sendRequest(prop, [...args]) as Promise<any>;
this.capture({ type: MessageType.SendRequest, requestId, serviceMethod: prop, arguments: args });
} else {
requestResult = connection.sendRequest(prop, ...args) as Promise<any>;
this.capture({ type: MessageType.SendRequest, requestId, serviceMethod: prop, arguments: args });
}

requestResult
Expand All @@ -126,8 +142,22 @@ export class RPCProxy {
const applicationError = ApplicationError.fromJson(result.error.code, result.error.data);
error.cause = applicationError;
}
this.capture({
type: MessageType.RequestResult,
status: ResponseStatus.Fail,
requestId,
serviceMethod: prop,
error: result.data,
});
reject(error);
} else {
this.capture({
type: MessageType.RequestResult,
status: ResponseStatus.Success,
requestId,
serviceMethod: prop,
data: result.data,
});
resolve(result.data);
}
});
Expand Down Expand Up @@ -162,9 +192,38 @@ export class RPCProxy {
const methods = this.getServiceMethod(service);
methods.forEach((method) => {
if (method.startsWith('on')) {
connection.onNotification(method, (...args) => this.onNotification(method, ...args));
connection.onNotification(method, (...args) => {
this.onNotification(method, ...args);
this.capture({ type: MessageType.OnNotification, serviceMethod: method, arguments: args });
});
} else {
connection.onRequest(method, (...args) => this.onRequest(method, ...args));
connection.onRequest(method, (...args) => {
const requestId = uuid();
const result = this.onRequest(method, ...args);
this.capture({ type: MessageType.OnRequest, requestId, serviceMethod: method, arguments: args });

result
.then((result) => {
this.capture({
type: MessageType.OnRequestResult,
status: ResponseStatus.Success,
requestId,
serviceMethod: method,
data: result.data,
});
})
.catch((err) => {
this.capture({
type: MessageType.OnRequestResult,
status: ResponseStatus.Fail,
requestId,
serviceMethod: method,
error: err.data,
});
});

return result;
});
}

if (cb) {
Expand All @@ -174,9 +233,19 @@ export class RPCProxy {

connection.onRequest((method) => {
if (!this.proxyService[method]) {
return {
const requestId = uuid();
this.capture({ type: MessageType.OnRequest, requestId, serviceMethod: method });
const result = {
data: NOTREGISTERMETHOD,
};
this.capture({
type: MessageType.OnRequestResult,
status: ResponseStatus.Fail,
requestId,
serviceMethod: method,
error: result.data,
});
return result;
}
});
}
Expand Down
37 changes: 37 additions & 0 deletions packages/connection/src/common/utils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,44 @@
declare global {
interface Window {
__OPENSUMI_DEVTOOLS_GLOBAL_HOOK__: any;
}
}

export enum MessageType {
SendNotification = 'sendNotification',
SendRequest = 'sendRequest',
RequestResult = 'requestResult',
OnNotification = 'onNotification',
OnRequest = 'onRequest',
OnRequestResult = 'onRequestResult',
}

export enum ResponseStatus {
Success = 'success',
Fail = 'fail',
}

export interface ICapturedMessage {
type: MessageType;
serviceMethod: string;
arguments?: any;
requestId?: string;
status?: ResponseStatus;
data?: any;
error?: any;
}

export function stringify(obj: any): string {
return JSON.stringify(obj);
}

export function parse(input: string, reviver?: (this: any, key: string, value: any) => any): any {
return JSON.parse(input, reviver);
}

export function getCapturer() {
if (typeof window !== 'undefined' && window.__OPENSUMI_DEVTOOLS_GLOBAL_HOOK__?.capture) {
return window.__OPENSUMI_DEVTOOLS_GLOBAL_HOOK__.capture;
}
return;
}
1 change: 0 additions & 1 deletion packages/connection/src/node/connect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
createMessageConnection,
} from '@opensumi/vscode-jsonrpc/lib/node/main';


export function createSocketConnection(socket: net.Socket) {
return createMessageConnection(new SocketMessageReader(socket), new SocketMessageWriter(socket));
}
4 changes: 4 additions & 0 deletions packages/core-browser/src/bootstrap/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,10 @@ export class ClientApp implements IClientApp, IDisposable {
stateService: ClientAppStateService;

constructor(opts: IClientAppOpts) {
// set a global so the opensumi devtools can identify that
// the current page is powered by opensumi core
window.__OPENSUMI_DEVTOOLS_GLOBAL_HOOK__ = {};

const {
modules,
contributions,
Expand Down