Skip to content

Commit

Permalink
Auth: get notified on auth change
Browse files Browse the repository at this point in the history
  • Loading branch information
abeatrix committed Aug 14, 2024
1 parent c144cba commit 2950aef
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 93 deletions.
13 changes: 9 additions & 4 deletions src/Cody.Core/Agent/IAgentService.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
using Cody.Core.Agent.Protocol;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Cody.Core.Agent
Expand Down Expand Up @@ -44,6 +40,15 @@ public interface IAgentService

[AgentMethod("textDocument/didClose")]
void DidClose(ProtocolTextDocument docState);

[AgentMethod("chat/new")]
Task<string> NewChat();

[AgentMethod("chat/sidebar/new")]
Task<ChatPanelInfo> NewSidebarChat();

[AgentMethod("chat/web/new")]
Task<ChatPanelInfo> NewEditorChat();
}
}

106 changes: 57 additions & 49 deletions src/Cody.Core/Agent/NotificationHandlers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using EnvDTE80;
using Newtonsoft.Json.Linq;
using System;
using System.Runtime.InteropServices;
using System.Threading.Tasks;

namespace Cody.Core.Agent
Expand All @@ -11,6 +12,7 @@ public class NotificationHandlers : INotificationHandler
public NotificationHandlers()
{
}

public delegate Task PostWebMessageAsJsonDelegate(string message);
public PostWebMessageAsJsonDelegate PostWebMessageAsJson { get; set; }

Expand All @@ -24,49 +26,32 @@ public NotificationHandlers()

public void SetAgentClient(IAgentService client)
{
this.agentClient = client;
agentClient = client;
agentClientReady.SetResult(true);
}

// Send a message to the host from webview.
public async Task SendWebviewMessage(string handle, string message)
{
// Turn message into a JSON object
var json = JObject.Parse(message);
var command = json["command"]?.ToString();

switch (command)
try
{
case "links":
var link = json["value"]?.ToString();
if (!string.IsNullOrEmpty(link))
{
// if the is links, open the link in the default browser
// string link = json["value"].ToString();
// System.Diagnostics.Process.Start(link);
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo(link) { UseShellExecute = true });
}
return;

case "command":
var json = JObject.Parse(message);
var command = json["command"]?.ToString();
if (command == "command")
{
var id = json["id"]?.ToString();
// Open the extension options page for authentication related commands.
if (id == "cody.status-bar.interacted" || id.StartsWith("cody.auth.signin") || id.StartsWith("cody.auth.signout"))
if (id == "cody.status-bar.interacted" || id?.StartsWith("cody.auth.signin") == true)
{
try
{
var dte = (DTE2)System.Runtime.InteropServices.Marshal.GetActiveObject("VisualStudio.DTE");
dte.ExecuteCommand("Tools.Options", "Cody.General");
}
catch (System.Runtime.InteropServices.COMException)
{
// Handle the case where Visual Studio is not running or COM object is not accessible
}
var dte = (DTE2)Marshal.GetActiveObject("VisualStudio.DTE");
dte.ExecuteCommand("Tools.Options", "Cody.General");
return;
}
break;
}
}
catch
{
// Ignore
}

await agentClient.ReceiveMessageStringEncoded(new ReceiveMessageStringEncodedParams
{
Id = handle,
Expand All @@ -77,18 +62,19 @@ await agentClient.ReceiveMessageStringEncoded(new ReceiveMessageStringEncodedPar
[AgentNotification("debug/message")]
public void Debug(string channel, string message)
{
System.Diagnostics.Debug.WriteLine(message, "Agent Debug");
DebugLog(message, "Debug");
}

[AgentNotification("webview/registerWebview")]
public void RegisterWebview(string handle)
{
System.Diagnostics.Debug.WriteLine(handle, "Agent registerWebview");
DebugLog(handle, "RegisterWebview");
}

[AgentNotification("webview/registerWebviewViewProvider")]
public async Task RegisterWebviewViewProvider(string viewId, bool retainContextWhenHidden)
{
DebugLog(viewId, "RegisterWebviewViewProvider");
agentClientReady.Task.Wait();
await agentClient.ResolveWebviewView(new ResolveWebviewViewParams
{
Expand All @@ -97,21 +83,20 @@ await agentClient.ResolveWebviewView(new ResolveWebviewViewParams
// TODO: Create dynmically when we support editor panel
WebviewHandle = "visual-studio-sidebar",
});
System.Diagnostics.Debug.WriteLine(viewId, retainContextWhenHidden, "Agent registerWebviewViewProvider");
}

[AgentNotification("webview/createWebviewPanel", deserializeToSingleObject: true)]
public void CreateWebviewPanel(CreateWebviewPanelParams panelParams)
{
System.Diagnostics.Debug.WriteLine(panelParams, "Agent createWebviewPanel");
DebugLog(panelParams.ToString(), "CreateWebviewPanel");
}

[AgentNotification("webview/setOptions")]
public void SetOptions(string handle, DefiniteWebviewOptions options)
{
if (options.EnableCommandUris is bool enableCmd)
{

DebugLog(handle, "SetOptions");
}
else if (options.EnableCommandUris is JArray jArray)
{
Expand All @@ -122,7 +107,7 @@ public void SetOptions(string handle, DefiniteWebviewOptions options)
[AgentNotification("webview/setHtml")]
public void SetHtml(string handle, string html)
{
System.Diagnostics.Debug.WriteLine(html, "Agent setHtml");
DebugLog(html, "SetHtml");
OnSetHtmlEvent?.Invoke(this, new SetHtmlEvent() { Handle = handle, Html = html });
}

Expand All @@ -135,45 +120,68 @@ public void PostMessage(string handle, string message)
[AgentNotification("webview/postMessageStringEncoded")]
public void PostMessageStringEncoded(string id, string stringEncodedMessage)
{
System.Diagnostics.Debug.WriteLine(stringEncodedMessage, "Agent postMessageStringEncoded");
DebugLog(stringEncodedMessage, "PostMessageStringEncoded");
PostWebMessageAsJson?.Invoke(stringEncodedMessage);
}

[AgentNotification("webview/didDisposeNative")]
public void DidDisposeNative(string handle)
{

}

[AgentNotification("extensionConfiguration/didChange", deserializeToSingleObject: true)]
public void ExtensionConfigDidChange(ExtensionConfiguration config)
{
System.Diagnostics.Debug.WriteLine(config, "Agent didChange");
DebugLog(handle, "DidDisposeNative");
}

[AgentNotification("webview/dispose")]
public void Dispose(string handle)
{
System.Diagnostics.Debug.WriteLine(handle, "Agent dispose");
DebugLog(handle, "Dispose");
}

[AgentNotification("webview/reveal")]
public void Reveal(string handle, int viewColumn, bool preserveFocus)
{
System.Diagnostics.Debug.WriteLine(handle, "Agent reveal");
DebugLog(handle, "Reveal");
}

[AgentNotification("webview/setTitle")]
public void SetTitle(string handle, string title)
{
System.Diagnostics.Debug.WriteLine(title, "Agent setTitle");
DebugLog(title, "SetTitle");
}

[AgentNotification("webview/setIconPath")]
public void SetIconPath(string handle, string iconPathUri)
{
System.Diagnostics.Debug.WriteLine(iconPathUri, "Agent setIconPath");
DebugLog(iconPathUri, "SetIconPath");
}

[AgentNotification("window/didChangeContext")]
public void WindowDidChangeContext(string key, string value)
{
DebugLog(value, $@"WindowDidChangeContext Key - {key}");

// Check the value to see if Cody is activated or deactivated
// Deactivated: value = "false", meaning user is no longer authenticated.
// In this case, we can send Agent a request to get the latest user AuthStatus to
// confirm if the user is logged out or not.
if (key == "cody.activated")
{
var isAuthenticated = value == "true";
DebugLog(isAuthenticated.ToString(), "User is authenticated");
}
}

[AgentNotification("extensionConfiguration/didChange", deserializeToSingleObject: true)]
public void ExtensionConfigDidChange(ExtensionConfiguration config)
{
DebugLog(config.ToString(), "didChange");
}

public void DebugLog(string message, string origin)
{
// Log the message to the debug console when in debug mode.
#if DEBUG
System.Diagnostics.Debug.WriteLine(message, $@"Agent Notified {origin}");
#endif
}
}
}
8 changes: 8 additions & 0 deletions src/Cody.Core/Agent/Protocol/ChatPanelInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Cody.Core.Agent.Protocol
{
public class ChatPanelInfo
{
public string PanelId { get; set; }
public string ChatId { get; set; }
}
}
1 change: 1 addition & 0 deletions src/Cody.Core/Cody.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
<Compile Include="Agent\INotificationHandler.cs" />
<Compile Include="Agent\NotificationHandlers.cs" />
<Compile Include="Agent\Protocol\AuthStatus.cs" />
<Compile Include="Agent\Protocol\ChatPanelInfo.cs" />
<Compile Include="Agent\Protocol\ClientCapabilities.cs" />
<Compile Include="Agent\Protocol\ClientInfo.cs" />
<Compile Include="Agent\Protocol\ConfigOverwrites.cs" />
Expand Down
64 changes: 24 additions & 40 deletions src/Cody.UI/Controls/WebviewController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ public class WebviewController
{
private CoreWebView2 _webview;

public CoreWebView2 GetWebview => _webview;

public string colorThemeScript;

public event EventHandler<string> WebViewMessageReceived;
Expand All @@ -31,24 +29,9 @@ public async Task<CoreWebView2> InitializeWebView(CoreWebView2 webView)
ConfigureWebView();
SetupResourceHandling();

webView.OpenDevToolsWindow();

return webView;
}

private async Task<CoreWebView2Environment> CreateWebView2Environment()
{
var appData = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Cody");
var options = new CoreWebView2EnvironmentOptions
{
#if DEBUG
AdditionalBrowserArguments = "--remote-debugging-port=9222 --disable-web-security --allow-file-access-from-files",
AllowSingleSignOnUsingOSPrimaryAccount = true,
#endif
};
return await CoreWebView2Environment.CreateAsync(null, appData, options);
}

private void ConfigureWebView()
{
_webview.Settings.AreDefaultScriptDialogsEnabled = true;
Expand All @@ -57,14 +40,19 @@ private void ConfigureWebView()
_webview.Settings.AreHostObjectsAllowed = true;
_webview.Settings.IsScriptEnabled = true;
_webview.Settings.AreBrowserAcceleratorKeysEnabled = true;
_webview.Settings.AreDevToolsEnabled = true;
_webview.Settings.IsGeneralAutofillEnabled = true;
// Enable below settings only in DEBUG mode.
_webview.Settings.AreDefaultContextMenusEnabled = false;
_webview.Settings.AreDevToolsEnabled = false;
#if DEBUG
_webview.Settings.AreDefaultContextMenusEnabled = true;
_webview.Settings.AreDevToolsEnabled = true;
#endif
}

private void SetupEventHandlers()
{
_webview.DOMContentLoaded += CoreWebView2OnDOMContentLoaded;
_webview.NavigationCompleted += CoreWebView2OnNavigationCompleted;
_webview.WebMessageReceived += HandleWebViewMessage;
}

Expand Down Expand Up @@ -103,31 +91,28 @@ private string GetContentType(string filePath)
return "Content-Type: text/html";
}


private async void CoreWebView2OnNavigationCompleted(object sender, CoreWebView2NavigationCompletedEventArgs e)
{
await ApplyThemingScript();
}

private async void CoreWebView2OnDOMContentLoaded(object sender, CoreWebView2DOMContentLoadedEventArgs e)
{
await ApplyThemingScript();
}

private void HandleWebViewMessage(object sender, CoreWebView2WebMessageReceivedEventArgs e)
{
// Handle message sent from webview to agent.
var message = e.TryGetWebMessageAsString();
System.Diagnostics.Debug.WriteLine(message, "Agent HandleWebViewMessage");
WebViewMessageReceived.Invoke(this, message);
// TODO: Get token from message if message has a token.
// IMPORTANT: Do not log the token to the console in production.
System.Diagnostics.Debug.WriteLine(message, "Agent HandleWebViewMessage");
}

public async Task PostWebMessageAsJson(string message)
{
await Application.Current.Dispatcher.InvokeAsync(async () =>
// From agent to webview.
await Application.Current.Dispatcher.InvokeAsync(() =>
{
System.Diagnostics.Debug.WriteLine(message, "Agent PostWebMessageAsJson");
_webview.PostWebMessageAsJson(message);
await _webview.ExecuteScriptWithResultAsync(GetPostMessageScript(message));
System.Diagnostics.Debug.WriteLine(message, "Agent PostWebMessageAsJson");
});
}

Expand All @@ -153,19 +138,27 @@ private static string GetVsCodeApiScript() => @"
globalThis.acquireVsCodeApi = (function() {
let acquired = false;
let state = undefined;
window.chrome.webview.addEventListener('message', e => {
console.log('Send to webview', e.data)
const event = new CustomEvent('message');
event.data = e.data;
window.dispatchEvent(event)
});
return () => {
if (acquired && !false) {
throw new Error('An instance of the VS Code API has already been acquired');
}
acquired = true;
return Object.freeze({
postMessage: function(message) {
console.log(`do-postMessage: ${JSON.stringify(message)}`);
console.log(`Send from webview: ${JSON.stringify(message)}`);
window.chrome.webview.postMessage(JSON.stringify(message));
},
setState: function(newState) {
state = newState;
console.log(`do-setState: ${JSON.stringify(newState)}`);
console.log(`Set State: ${JSON.stringify(newState)}`);
return newState;
},
getState: function() {
Expand All @@ -184,14 +177,5 @@ private static string GetThemeScript(string colorTheme) => $@"
{colorTheme}
";

private static string GetPostMessageScript(string message) => $@"
(() => {{
const event = new CustomEvent('message');
console.log('PostWebMessageAsJson', {message});
event.data = {message};
window.dispatchEvent(event);
}})()
";
}
}

0 comments on commit 2950aef

Please sign in to comment.