Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
146 changes: 75 additions & 71 deletions src/Core/src/Handlers/HybridWebView/HybridWebView.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@
window.dispatchEvent(event);
}
// Determine the mechanism to receive messages from the host application.
if (window.chrome?.webview?.addEventListener) {
if (window.chrome && window.chrome.webview && window.chrome.webview.addEventListener) {
// Windows WebView2
window.chrome.webview.addEventListener('message', (arg) => {
dispatchHybridWebViewMessage(arg.data);
});
}
else if (window.webkit?.messageHandlers?.webwindowinterop) {
else if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.webwindowinterop) {
Comment on lines +21 to +27
Copy link

Copilot AI Jun 6, 2025

Choose a reason for hiding this comment

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

[nitpick] The explicit property checks here replace optional chaining to support older environments. Consider a utility function if similar nested checks are needed in other parts of the codebase.

Copilot uses AI. Check for mistakes.
// iOS and MacCatalyst WKWebView
// @ts-ignore - We are extending the global object here
window.external = {
Expand All @@ -40,11 +40,11 @@
});
}
// Determine the function to use to send messages to the host application.
if (window.chrome?.webview) {
if (window.chrome && window.chrome.webview) {
// Windows WebView2
sendMessageFunction = msg => window.chrome.webview.postMessage(msg);
}
else if (window.webkit?.messageHandlers?.webwindowinterop) {
else if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.webwindowinterop) {
// iOS and MacCatalyst WKWebView
sendMessageFunction = msg => window.webkit.messageHandlers.webwindowinterop.postMessage(msg);
}
Expand Down Expand Up @@ -108,78 +108,82 @@
const json = JSON.stringify(errorObj);
sendMessageToDotNet('__InvokeJavaScriptFailed', taskId + '|' + json);
}
const HybridWebView = {
/*
* Send a raw message to the .NET host application.
* The message is sent directly and not processed or serialized.
*
* @param message The message to send to the .NET host application.
*/
SendRawMessage: function (message) {
sendMessageToDotNet('__RawMessage', message);
},
/*
* Invoke a .NET method on the InvokeJavaScriptTarget instance.
* The method name and parameters are serialized and sent to the .NET host application.
*
* @param methodName The name of the .NET method to invoke.
* @param paramValues The parameters to pass to the .NET method. If the method takes no parameters, this can be omitted.
*
* @returns A promise that resolves with the result of the .NET method invocation.
*/
InvokeDotNet: async function (methodName, paramValues) {
const body = {
MethodName: methodName
};
// if parameters were provided, serialize them first
if (paramValues !== undefined) {
if (!Array.isArray(paramValues)) {
paramValues = [paramValues];
}
for (let i = 0; i < paramValues.length; i++) {
paramValues[i] = JSON.stringify(paramValues[i]);
}
if (paramValues.length > 0) {
body.ParamValues = paramValues;
}
}
const message = JSON.stringify(body);
const requestUrl = `${window.location.origin}/__hwvInvokeDotNet?data=${encodeURIComponent(message)}`;
const rawResponse = await fetch(requestUrl, {
method: 'GET',
headers: {
'Accept': 'application/json'
}
});
const response = await rawResponse.json();
if (!response) {
return null;
/*
* Send a raw message to the .NET host application.
* The message is sent directly and not processed or serialized.
*
* @param message The message to send to the .NET host application.
*/
function sendRawMessage(message) {
sendMessageToDotNet('__RawMessage', message);
}
/*
* Invoke a .NET method on the InvokeJavaScriptTarget instance.
* The method name and parameters are serialized and sent to the .NET host application.
*
* @param methodName The name of the .NET method to invoke.
* @param paramValues The parameters to pass to the .NET method. If the method takes no parameters, this can be omitted.
*
* @returns A promise that resolves with the result of the .NET method invocation.
*/
async function invokeDotNet(methodName, paramValues) {
const body = {
MethodName: methodName
};
// if parameters were provided, serialize them first
if (paramValues !== undefined) {
if (!Array.isArray(paramValues)) {
paramValues = [paramValues];
}
if (response.IsJson) {
return JSON.parse(response.Result);
for (let i = 0; i < paramValues.length; i++) {
paramValues[i] = JSON.stringify(paramValues[i]);
}
return response.Result;
},
/*
* Invoke a JavaScript method from the .NET host application.
* This method is called from the HybridWebViewHandler and is not intended to be used by user applications.
*
* @param taskId The task ID that was provided by the .NET host application.
* @param methodName The JavaScript method to invoke in the global scope.
* @param args The arguments to pass to the JavaScript method.
*
* @returns A promise.
*/
__InvokeJavaScript: async function (taskId, methodName, args) {
try {
const result = await methodName(...args);
invokeJavaScriptCallbackInDotNet(taskId, result);
if (paramValues.length > 0) {
body.ParamValues = paramValues;
}
catch (ex) {
console.error(ex);
invokeJavaScriptFailedInDotNet(taskId, ex);
}
const message = JSON.stringify(body);
const requestUrl = `${window.location.origin}/__hwvInvokeDotNet?data=${encodeURIComponent(message)}`;
const rawResponse = await fetch(requestUrl, {
method: 'GET',
headers: {
'Accept': 'application/json'
}
});
const response = await rawResponse.json();
if (!response) {
return null;
}
if (response.IsJson) {
return JSON.parse(response.Result);
}
return response.Result;
}
/*
* Invoke a JavaScript method from the .NET host application.
* This method is called from the HybridWebViewHandler and is not intended to be used by user applications.
*
* @param taskId The task ID that was provided by the .NET host application.
* @param methodName The JavaScript method to invoke in the global scope.
* @param args The arguments to pass to the JavaScript method.
*
* @returns A promise.
*/
async function invokeJavaScript(taskId, methodName, args) {
try {
const result = await methodName(...args);
invokeJavaScriptCallbackInDotNet(taskId, result);
}
catch (ex) {
console.error(ex);
invokeJavaScriptFailedInDotNet(taskId, ex);
}
}
// Define the public API of the HybridWebView control.
const HybridWebView = {
SendRawMessage: sendRawMessage,
InvokeDotNet: invokeDotNet,
__InvokeJavaScript: invokeJavaScript
};
// Make the following APIs available in global scope for invocation from JS
// @ts-ignore - We are extending the global object here
Expand Down
154 changes: 79 additions & 75 deletions src/Core/src/Handlers/HybridWebView/HybridWebView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,12 @@ interface DotNetInvokeResult {
}

// Determine the mechanism to receive messages from the host application.
if (window.chrome?.webview?.addEventListener) {
if (window.chrome && window.chrome.webview && window.chrome.webview.addEventListener) {
// Windows WebView2
window.chrome.webview.addEventListener('message', (arg: any) => {
dispatchHybridWebViewMessage(arg.data);
});
} else if (window.webkit?.messageHandlers?.webwindowinterop) {
} else if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.webwindowinterop) {
Comment on lines +71 to +76
Copy link

Copilot AI Jun 6, 2025

Choose a reason for hiding this comment

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

[nitpick] Explicitly checking for each nested property improves compatibility with older platforms. If this pattern recurs, consider extracting it into a helper function to improve readability and maintainability.

Copilot uses AI. Check for mistakes.
// iOS and MacCatalyst WKWebView
// @ts-ignore - We are extending the global object here
window.external = {
Expand All @@ -89,10 +89,10 @@ interface DotNetInvokeResult {
}

// Determine the function to use to send messages to the host application.
if (window.chrome?.webview) {
if (window.chrome && window.chrome.webview) {
// Windows WebView2
sendMessageFunction = msg => window.chrome.webview.postMessage(msg);
} else if (window.webkit?.messageHandlers?.webwindowinterop) {
} else if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.webwindowinterop) {
// iOS and MacCatalyst WKWebView
sendMessageFunction = msg => window.webkit.messageHandlers.webwindowinterop.postMessage(msg);
} else if (window.hybridWebViewHost) {
Expand Down Expand Up @@ -160,89 +160,93 @@ interface DotNetInvokeResult {
sendMessageToDotNet('__InvokeJavaScriptFailed', taskId + '|' + json);
}

const HybridWebView = {

/*
* Send a raw message to the .NET host application.
* The message is sent directly and not processed or serialized.
*
* @param message The message to send to the .NET host application.
*/
SendRawMessage: function (message: string) {
sendMessageToDotNet('__RawMessage', message);
},

/*
* Invoke a .NET method on the InvokeJavaScriptTarget instance.
* The method name and parameters are serialized and sent to the .NET host application.
*
* @param methodName The name of the .NET method to invoke.
* @param paramValues The parameters to pass to the .NET method. If the method takes no parameters, this can be omitted.
*
* @returns A promise that resolves with the result of the .NET method invocation.
*/
InvokeDotNet: async function (methodName: string, paramValues?: any) {
const body: JSInvokeMethodData = {
MethodName: methodName
};
/*
* Send a raw message to the .NET host application.
* The message is sent directly and not processed or serialized.
*
* @param message The message to send to the .NET host application.
*/
function sendRawMessage(message: string) {
Copy link

Copilot AI Jun 6, 2025

Choose a reason for hiding this comment

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

[nitpick] Refactoring inline object literal methods into standalone function declarations enhances clarity and modularity. Please ensure consistency in naming and ordering across similar functions for a uniform API.

Suggested change
function sendRawMessage(message: string) {
export function sendRawMessage(message: string) {

Copilot uses AI. Check for mistakes.
sendMessageToDotNet('__RawMessage', message);
}

// if parameters were provided, serialize them first
if (paramValues !== undefined) {
if (!Array.isArray(paramValues)) {
paramValues = [paramValues];
}
/*
* Invoke a .NET method on the InvokeJavaScriptTarget instance.
* The method name and parameters are serialized and sent to the .NET host application.
*
* @param methodName The name of the .NET method to invoke.
* @param paramValues The parameters to pass to the .NET method. If the method takes no parameters, this can be omitted.
*
* @returns A promise that resolves with the result of the .NET method invocation.
*/
async function invokeDotNet(methodName: string, paramValues?: any) {
const body: JSInvokeMethodData = {
MethodName: methodName
};

for (let i = 0; i < paramValues.length; i++) {
paramValues[i] = JSON.stringify(paramValues[i]);
}
// if parameters were provided, serialize them first
if (paramValues !== undefined) {
if (!Array.isArray(paramValues)) {
paramValues = [paramValues];
}

if (paramValues.length > 0) {
body.ParamValues = paramValues;
}
for (let i = 0; i < paramValues.length; i++) {
paramValues[i] = JSON.stringify(paramValues[i]);
}

const message = JSON.stringify(body);
if (paramValues.length > 0) {
body.ParamValues = paramValues;
}
}

const requestUrl = `${window.location.origin}/__hwvInvokeDotNet?data=${encodeURIComponent(message)}`;
const message = JSON.stringify(body);

const rawResponse = await fetch(requestUrl, {
method: 'GET',
headers: {
'Accept': 'application/json'
}
});
const response: DotNetInvokeResult = await rawResponse.json();
const requestUrl = `${window.location.origin}/__hwvInvokeDotNet?data=${encodeURIComponent(message)}`;

if (!response) {
return null;
const rawResponse = await fetch(requestUrl, {
method: 'GET',
headers: {
'Accept': 'application/json'
}
});
const response: DotNetInvokeResult = await rawResponse.json();

if (response.IsJson) {
return JSON.parse(response.Result);
}
if (!response) {
return null;
}

return response.Result;
},

/*
* Invoke a JavaScript method from the .NET host application.
* This method is called from the HybridWebViewHandler and is not intended to be used by user applications.
*
* @param taskId The task ID that was provided by the .NET host application.
* @param methodName The JavaScript method to invoke in the global scope.
* @param args The arguments to pass to the JavaScript method.
*
* @returns A promise.
*/
__InvokeJavaScript: async function (taskId: string, methodName: Function, args: any[]) {
try {
const result = await methodName(...args);
invokeJavaScriptCallbackInDotNet(taskId, result);
} catch (ex) {
console.error(ex);
invokeJavaScriptFailedInDotNet(taskId, ex);
}
if (response.IsJson) {
return JSON.parse(response.Result);
}

return response.Result;
}

/*
* Invoke a JavaScript method from the .NET host application.
* This method is called from the HybridWebViewHandler and is not intended to be used by user applications.
*
* @param taskId The task ID that was provided by the .NET host application.
* @param methodName The JavaScript method to invoke in the global scope.
* @param args The arguments to pass to the JavaScript method.
*
* @returns A promise.
*/
async function invokeJavaScript(taskId: string, methodName: Function, args: any[]) {
try {
const result = await methodName(...args);
invokeJavaScriptCallbackInDotNet(taskId, result);
} catch (ex) {
console.error(ex);
invokeJavaScriptFailedInDotNet(taskId, ex);
}
}

// Define the public API of the HybridWebView control.
const HybridWebView = {
SendRawMessage: sendRawMessage,
InvokeDotNet: invokeDotNet,
__InvokeJavaScript: invokeJavaScript
};

// Make the following APIs available in global scope for invocation from JS
Expand Down
Loading