Skip to content

Commit 3b8fb14

Browse files
authored
feat(debugger): Can add custom Minecraft command buttons to MC panel, and optional passcode required for connection. (#205)
1 parent 94c9896 commit 3b8fb14

File tree

8 files changed

+249
-95
lines changed

8 files changed

+249
-95
lines changed

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,10 @@
125125
"targetModuleUuid": {
126126
"type": "string",
127127
"description": "The script module uuid from the manifest.json of the Minecraft Add-On being debugged. Necessary if there are multiple Add-Ons active."
128+
},
129+
"passcode": {
130+
"type": "string",
131+
"description": "If set, user will not be prompted for passcode if Minecraft is expecting one."
128132
}
129133
}
130134
}

src/ServerDebugAdapterFactory.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { StatsProvider2 } from './StatsProvider2';
1010
export class ServerDebugAdapterFactory implements vscode.DebugAdapterDescriptorFactory {
1111
private server?: Net.Server;
1212

13-
constructor(private _statProvider2: StatsProvider2) {}
13+
constructor(private _statProvider2: StatsProvider2, private _eventEmitter: any) {}
1414

1515
createDebugAdapterDescriptor(
1616
session: vscode.DebugSession,
@@ -19,7 +19,7 @@ export class ServerDebugAdapterFactory implements vscode.DebugAdapterDescriptorF
1919
if (!this.server) {
2020
// start listening on a random port
2121
this.server = Net.createServer(socket => {
22-
const session = new Session(this._statProvider2);
22+
const session = new Session(this._statProvider2, this._eventEmitter);
2323
session.setRunAsServer(true);
2424
session.start(socket as NodeJS.ReadableStream, socket);
2525
}).listen(0);

src/Session.ts

Lines changed: 86 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,24 @@ import {
1313
ThreadEvent,
1414
Variable,
1515
} from '@vscode/debugadapter';
16-
import { commands, FileSystemWatcher, QuickPickItem, QuickPickOptions, workspace, window } from 'vscode';
16+
import {
17+
commands,
18+
FileSystemWatcher,
19+
InputBoxOptions,
20+
QuickPickItem,
21+
QuickPickOptions,
22+
workspace,
23+
window,
24+
} from 'vscode';
1725
import { DebugProtocol } from '@vscode/debugprotocol';
26+
import { EventEmitter } from 'events';
1827
import { LogOutputEvent, LogLevel } from '@vscode/debugadapter/lib/logger';
1928
import { MessageStreamParser } from './MessageStreamParser';
2029
import { SourceMaps } from './SourceMaps';
30+
import { StatMessageModel, StatsProvider2 } from './StatsProvider2';
2131
import * as path from 'path';
2232
import * as fs from 'fs';
2333
import { isUUID } from './Utils';
24-
import { StatMessageModel, StatsProvider2 } from './StatsProvider2';
2534

2635
interface PendingResponse {
2736
onSuccess?: Function;
@@ -42,6 +51,7 @@ interface ProtocolCapabilities {
4251
type: string;
4352
version: number;
4453
plugins: PluginDetails[];
54+
require_passcode?: boolean;
4555
}
4656

4757
// Interface for specific launch arguments.
@@ -58,6 +68,7 @@ interface IAttachRequestArguments extends DebugProtocol.AttachRequestArguments {
5868
moduleMapping?: ModuleMapping;
5969
sourceMapBias?: string;
6070
targetModuleUuid?: string;
71+
passcode?: string;
6172
}
6273

6374
class TargetPluginItem implements QuickPickItem {
@@ -76,16 +87,18 @@ class TargetPluginItem implements QuickPickItem {
7687
// 1 - initial version
7788
// 2 - add targetModuleUuid to protocol event
7889
// 3 - add array of plugins and target module ids to incoming protocol event
90+
// 4 - mc can require a passcode to connect
7991
enum ProtcolVersion {
8092
Initial = 1,
8193
SupportTargetModuleUuid = 2,
8294
SupportTargetSelection = 3,
95+
SupportPasscode = 4,
8396
}
8497

8598
// The Debug Adapter for 'minecraft-js'
8699
//
87100
export class Session extends DebugSession {
88-
private static DEBUGGER_PROTOCOL_VERSION = ProtcolVersion.SupportTargetSelection;
101+
private static DEBUGGER_PROTOCOL_VERSION = ProtcolVersion.SupportPasscode;
89102

90103
private static CONNECTION_RETRY_ATTEMPTS = 5;
91104
private static CONNECTION_RETRY_WAIT_MS = 2000;
@@ -105,12 +118,27 @@ export class Session extends DebugSession {
105118
private _moduleMapping?: ModuleMapping;
106119
private _sourceMapBias?: string;
107120
private _targetModuleUuid?: string;
121+
private _passcode?: string;
122+
private _statsProvider: StatsProvider2;
123+
private _eventEmitter: any;
108124

109-
public constructor(private _statsProvider2: StatsProvider2) {
125+
public constructor(statsProvider: StatsProvider2, eventEmitter: EventEmitter) {
110126
super();
111127

128+
this._statsProvider = statsProvider;
129+
this._eventEmitter = eventEmitter;
130+
112131
this.setDebuggerLinesStartAt1(true);
113132
this.setDebuggerColumnsStartAt1(true);
133+
134+
// listen for events from the HomeViewProvider
135+
this._eventEmitter.on('run-minecraft-command', (command: string) => {
136+
this.sendDebuggeeMessage({
137+
type: 'minecraftCommand',
138+
command: command,
139+
dimension_type: 'overworld', // todo: get this from the user
140+
});
141+
});
114142
}
115143

116144
// ------------------------------------------------------------------------
@@ -169,6 +197,7 @@ export class Session extends DebugSession {
169197
if (args.targetModuleUuid && isUUID(args.targetModuleUuid)) {
170198
this._targetModuleUuid = args.targetModuleUuid.toLowerCase();
171199
}
200+
this._passcode = args.passcode;
172201

173202
this._localRoot = args.localRoot ? path.normalize(args.localRoot) : '';
174203
this._sourceMapRoot = args.sourceMapRoot ? path.normalize(args.sourceMapRoot) : undefined;
@@ -505,14 +534,15 @@ export class Session extends DebugSession {
505534
//
506535
}
507536

508-
private onConnectionComplete(targetModuleUuid?: string) {
537+
private onConnectionComplete(targetModuleUuid?: string, passcode?: string) {
509538
this._targetModuleUuid = targetModuleUuid;
510539

511540
// respond with protocol version and chosen debugee target
512541
this.sendDebuggeeMessage({
513542
type: 'protocol',
514543
version: Session.DEBUGGER_PROTOCOL_VERSION,
515544
target_module_uuid: targetModuleUuid,
545+
passcode: passcode,
516546
});
517547

518548
// show notifications for source map issues
@@ -665,7 +695,7 @@ export class Session extends DebugSession {
665695
} else if (eventMessage.type === 'ProtocolEvent') {
666696
this.handleProtocolEvent(eventMessage as ProtocolCapabilities);
667697
} else if (eventMessage.type === 'StatEvent2') {
668-
this._statsProvider2.setStats(eventMessage as StatMessageModel);
698+
this._statsProvider.setStats(eventMessage as StatMessageModel);
669699
}
670700
}
671701

@@ -730,7 +760,7 @@ export class Session extends DebugSession {
730760
// ------------------------------------------------------------------------
731761

732762
// the final client event before connection is complete
733-
private handleProtocolEvent(protocolCapabilities: ProtocolCapabilities) {
763+
private async handleProtocolEvent(protocolCapabilities: ProtocolCapabilities): Promise<void> {
734764
//
735765
// handle protocol capabilities here...
736766
// can fail connection on errors
@@ -739,17 +769,24 @@ export class Session extends DebugSession {
739769
this.terminateSession('protocol mismatch. Update Debugger Extension.', LogLevel.Error);
740770
} else {
741771
if (protocolCapabilities.version == ProtcolVersion.SupportTargetModuleUuid) {
742-
this.onConnectionComplete(this._targetModuleUuid);
743-
} else if (protocolCapabilities.version == ProtcolVersion.SupportTargetSelection) {
772+
this.onConnectionComplete(this._targetModuleUuid, undefined);
773+
} else if (protocolCapabilities.version >= ProtcolVersion.SupportTargetSelection) {
774+
// no add-ons found, nothing to do
744775
if (!protocolCapabilities.plugins || protocolCapabilities.plugins.length === 0) {
745776
this.terminateSession('protocol error. No Minecraft Add-Ons found.', LogLevel.Error);
746777
return;
747-
} else if (this._targetModuleUuid) {
778+
}
779+
780+
// if passcode is required, prompt user for it
781+
let passcode = await this.promptForPasscode(protocolCapabilities.require_passcode);
782+
783+
// if a targetuuid was provided, make sure it's valid
784+
if (this._targetModuleUuid) {
748785
const isValidTarget = protocolCapabilities.plugins.some(
749786
plugin => plugin.module_uuid === this._targetModuleUuid
750787
);
751788
if (isValidTarget) {
752-
this.onConnectionComplete(this._targetModuleUuid);
789+
this.onConnectionComplete(this._targetModuleUuid, passcode);
753790
return;
754791
} else {
755792
this.showNotification(
@@ -758,7 +795,7 @@ export class Session extends DebugSession {
758795
);
759796
}
760797
} else if (protocolCapabilities.plugins.length === 1) {
761-
this.onConnectionComplete(protocolCapabilities.plugins[0].module_uuid);
798+
this.onConnectionComplete(protocolCapabilities.plugins[0].module_uuid, passcode);
762799
return;
763800
} else {
764801
this.showNotification(
@@ -767,32 +804,50 @@ export class Session extends DebugSession {
767804
);
768805
}
769806

770-
//
771807
// Could not connect automatically, prompt user to select target.
772-
//
773-
const items: TargetPluginItem[] = protocolCapabilities.plugins.map(
774-
plugin => new TargetPluginItem(plugin)
775-
);
776-
const options: QuickPickOptions = {
777-
title: 'Choose the Minecraft Add-On to debug',
778-
ignoreFocusOut: true,
779-
};
780-
window.showQuickPick(items, options).then(value => {
781-
if (!value) {
782-
this.terminateSession(
783-
'could not determine target Minecraft Add-On. You must specify the targetModuleUuid.',
784-
LogLevel.Error
785-
);
786-
} else {
787-
this.onConnectionComplete(value?.targetModuleId);
788-
}
789-
});
808+
const targetUuid = await this.promptForTargetPlugin(protocolCapabilities.plugins);
809+
if (!targetUuid) {
810+
this.terminateSession(
811+
'could not determine target Minecraft Add-On. You must specify the targetModuleUuid.',
812+
LogLevel.Error
813+
);
814+
return;
815+
}
816+
this.onConnectionComplete(targetUuid, passcode);
790817
} else {
791818
this.terminateSession('protocol unsupported. Update Debugger Extension.', LogLevel.Error);
792819
}
793820
}
794821
}
795822

823+
private async promptForPasscode(requirePasscode?: boolean): Promise<string | undefined> {
824+
if (requirePasscode) {
825+
if (this._passcode) {
826+
return this._passcode;
827+
} else {
828+
const options: InputBoxOptions = {
829+
title: 'Enter Passcode',
830+
ignoreFocusOut: true,
831+
};
832+
return await window.showInputBox(options);
833+
}
834+
}
835+
return undefined;
836+
}
837+
838+
private async promptForTargetPlugin(plugins: PluginDetails[]): Promise<string | undefined> {
839+
const items: TargetPluginItem[] = plugins.map(plugin => new TargetPluginItem(plugin));
840+
const options: QuickPickOptions = {
841+
title: 'Choose the Minecraft Add-On to debug',
842+
ignoreFocusOut: true,
843+
};
844+
const targetItem = await window.showQuickPick(items, options);
845+
if (targetItem) {
846+
return targetItem.targetModuleId;
847+
}
848+
return undefined;
849+
}
850+
796851
// check that source and map properties in launch.json are set correctly
797852
private checkSourceFilePaths() {
798853
if (this._sourceMapRoot) {

src/extension.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,17 @@ import { ServerDebugAdapterFactory } from './ServerDebugAdapterFactory';
66
import { HomeViewProvider } from './panels/HomeViewProvider';
77
import { MinecraftDiagnosticsPanel } from './panels/MinecraftDiagnostics';
88
import { StatsProvider2 } from './StatsProvider2';
9+
import { EventEmitter } from 'stream';
910

1011
// called when extension is activated
1112
//
1213
export function activate(context: vscode.ExtensionContext) {
1314
// create tree data providers and register them
1415
const statProvider2 = new StatsProvider2();
16+
const eventEmitter = new EventEmitter();
1517

1618
// home view
17-
const homeViewProvider = new HomeViewProvider(context.extensionUri);
19+
const homeViewProvider = new HomeViewProvider(context.extensionUri, eventEmitter);
1820
context.subscriptions.push(vscode.window.registerWebviewViewProvider(HomeViewProvider.viewType, homeViewProvider));
1921

2022
// register commands
@@ -32,7 +34,7 @@ export function activate(context: vscode.ExtensionContext) {
3234
context.subscriptions.push(vscode.debug.registerDebugConfigurationProvider('minecraft-js', configProvider));
3335

3436
// register a debug adapter descriptor factory for 'minecraft-js', this factory creates the DebugSession
35-
let descriptorFactory = new ServerDebugAdapterFactory(statProvider2);
37+
let descriptorFactory = new ServerDebugAdapterFactory(statProvider2, eventEmitter);
3638
context.subscriptions.push(vscode.debug.registerDebugAdapterDescriptorFactory('minecraft-js', descriptorFactory));
3739

3840
if ('dispose' in descriptorFactory) {

src/panels/HomeViewProvider.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,19 @@
33
import * as vscode from 'vscode';
44
import { getNonce } from '../utilities/getNonce';
55
import { getUri } from '../utilities/getUri';
6+
import { EventEmitter } from 'stream';
67

78
export class HomeViewProvider implements vscode.WebviewViewProvider {
89
public static readonly viewType = 'minecraft-debugger-home-panel';
910

11+
private readonly _extensionUri: vscode.Uri;
12+
private _eventEmitter: EventEmitter;
1013
private _view?: vscode.WebviewView;
1114

12-
constructor(private readonly _extensionUri: vscode.Uri) {}
15+
constructor(extensionUri: vscode.Uri, eventEmitter: EventEmitter) {
16+
this._extensionUri = extensionUri;
17+
this._eventEmitter = eventEmitter;
18+
}
1319

1420
public resolveWebviewView(
1521
webviewView: vscode.WebviewView,
@@ -18,24 +24,30 @@ export class HomeViewProvider implements vscode.WebviewViewProvider {
1824
) {
1925
this._view = webviewView;
2026

21-
webviewView.webview.options = {
27+
this._view.webview.options = {
2228
enableScripts: true,
2329
localResourceRoots: [this._extensionUri],
2430
};
2531

26-
webviewView.webview.html = this._getHtmlForWebview(webviewView.webview, this._extensionUri);
32+
this._view.webview.html = this._getHtmlForWebview(this._view.webview, this._extensionUri);
2733

28-
webviewView.webview.onDidReceiveMessage(data => {
34+
this._view.webview.onDidReceiveMessage(data => {
2935
switch (data.type) {
3036
case 'show-diagnostics': {
3137
vscode.commands.executeCommand('minecraft-debugger.showMinecraftDiagnostics');
3238
break;
3339
}
40+
case 'run-minecraft-command': {
41+
this._eventEmitter.emit('run-minecraft-command', data.command);
42+
break;
43+
}
3444
}
3545
});
3646
}
3747

3848
private _getHtmlForWebview(webview: vscode.Webview, extensionUri: vscode.Uri) {
49+
const stylesUri = getUri(webview, extensionUri, ['webview-ui', 'build', 'assets', 'homePanel.css']);
50+
3951
// The JS file from the React build output
4052
const scriptUri = getUri(webview, extensionUri, ['webview-ui', 'build', 'assets', 'homePanel.js']);
4153

@@ -48,6 +60,7 @@ export class HomeViewProvider implements vscode.WebviewViewProvider {
4860
<meta charset="UTF-8" />
4961
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src ${webview.cspSource}; script-src 'nonce-${nonce}';">
5062
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
63+
<link rel="stylesheet" type="text/css" href="${stylesUri}">
5164
<title>Minecraft Home</title>
5265
</head>
5366
<body>

0 commit comments

Comments
 (0)