{"id":18009929,"url":"https://github.com/kekyo/dupenukem","last_synced_at":"2025-06-16T03:04:45.602Z","repository":{"id":38202387,"uuid":"467868415","full_name":"kekyo/DupeNukem","owner":"kekyo","description":"WebView attachable full-duplex asynchronous interoperable independent messaging library between .NET and JavaScript.","archived":false,"fork":false,"pushed_at":"2024-05-23T06:15:24.000Z","size":1620,"stargazers_count":18,"open_issues_count":6,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2024-10-30T03:48:47.243Z","etag":null,"topics":["android","asynchronous","attachable","cefsharp","dotnet","edge2","full-duplex","independent","interoperable","javascript","messaging","webview"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kekyo.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-03-09T09:53:33.000Z","updated_at":"2024-08-23T10:21:26.000Z","dependencies_parsed_at":"2024-02-13T05:23:15.030Z","dependency_job_id":"147f326c-4150-4bf6-bad6-70787ca2b8c1","html_url":"https://github.com/kekyo/DupeNukem","commit_stats":{"total_commits":98,"total_committers":2,"mean_commits":49.0,"dds":"0.010204081632653073","last_synced_commit":"256167bc9173723a814a2871b6ce5230439c8257"},"previous_names":[],"tags_count":32,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kekyo%2FDupeNukem","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kekyo%2FDupeNukem/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kekyo%2FDupeNukem/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kekyo%2FDupeNukem/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kekyo","download_url":"https://codeload.github.com/kekyo/DupeNukem/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245670747,"owners_count":20653413,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["android","asynchronous","attachable","cefsharp","dotnet","edge2","full-duplex","independent","interoperable","javascript","messaging","webview"],"created_at":"2024-10-30T02:11:35.428Z","updated_at":"2025-03-26T14:31:37.700Z","avatar_url":"https://github.com/kekyo.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# DupeNukem\n\n![DupeNukem](https://github.com/kekyo/DupeNukem/raw/main/Images/DupeNukem.100.png)\n\nDupeNukem - WebView attachable full-duplex asynchronous interoperable independent messaging library between .NET and JavaScript.\n\n[![Project Status: WIP – Initial development is in progress, but there has not yet been a stable, usable release suitable for the public.](https://www.repostatus.org/badges/latest/wip.svg)](https://www.repostatus.org/#wip)\n\n## NuGet\n\n|Package|NuGet|\n|:--|:--|\n|DupeNukem|[![NuGet DupeNukem](https://img.shields.io/nuget/v/DupeNukem.svg?style=flat)](https://www.nuget.org/packages/DupeNukem)|\n|DupeNukem.Core|[![NuGet DupeNukem.Core](https://img.shields.io/nuget/v/DupeNukem.Core.svg?style=flat)](https://www.nuget.org/packages/DupeNukem.Core)|\n\n----\n\n## What is this?\n\nGeneral purpose `WebView` attachable independent messaging (RPC like) library.\n\nThis library is intended for use with a browser component called `WebView` (Edge2, CefSharp, Android, Celenium and etc) where asynchronous interoperation is not possible or is limited.\nIt is also independent of any specific `WebView` implementation, so it can be applied to any `WebView` you use.\nThe only requirement is to be able to send and receive strings to and from each other.\n\nThis is a diagrammatic representation of the message transfer performed by DupeNukem.\n\n.NET side to call a function on the JavaScript side, the `InvokePeerMethodAsync` method returns a `Task`, so it can wait asynchronously:\n\n![.NET world to JavaScript invoking](Images/diagram1.png)\n\nSimilarly, JavaScript side to call a method on the .NET side, the `invokeHostMethod` function returns `Promise`, so it can wait asynchronously too:\n\n![.NET world to JavaScript invoking](Images/diagram2.png)\n\nIt is complemental design. Both .NET and JavaScript, we can design methods and functions assuming a nearly identical structure.\nAnd with DupeNukem, you can use it for multi-platform `WebView` based applications without having to use different implementations for each `WebView` interface. The implementation can be standardized.\n\nThis may seem simple at first glance, but there are some difficult issues to be addressed, such as the following:\n\n* Each call must be distinguished individually.\n  DupeNukem manages each call and correctly distinguishes between them, even if multiple calls exist in parallel. (Yes, it is ready for asynchronous parallelism using `Task.WhenAll` and `Promise.all` and like.)\n* On `WebView`, only strings must be used as a means of communication.\n  DupeNukem uses JSON as the communication format, but the user does not need to be aware of it, except for custom type conversions.\n  This can be thought of as the same as the custom type constraints used for sending and receiving in ASP.NET WebAPI, etc.\n\n## Example\n\nReally? Now let's look at the actual calling code both side.\n\nInvoke JavaScript functions from .NET side:\n\n```csharp\nvar result_add = await messenger.InvokePeerMethodAsync\u003cint\u003e(\n    \"js_add\", 1, 2);\n\nvar result_sub = await messenger.InvokePeerMethodAsync\u003cint\u003e(\n    \"js_sub\", 1, 2);\n```\n\nInvoke .NET methods from JavaScript side (using proxy objects):\n\n```javascript\n// `Add` method\nconst result_Add = await dupeNukem.viewModels.calculator.add(1, 2);\n\n// `dotnet_add` delegate\nconst result_add = await dotnet_add(1, 2);\n```\n\nHere is an example using:\n\n* [`Microsoft.Web.WebView2`](https://www.nuget.org/packages/Microsoft.Web.WebView2) on WPF. ([Fully sample code is here](https://github.com/kekyo/DupeNukem/blob/main/samples/DupeNukem.WebView2/ViewModels/MainWindowViewModel.cs))\n* [`CefSharp.Wpf`](https://www.nuget.org/packages/CefSharp.Wpf) on WPF. ([Fully sample code is here](https://github.com/kekyo/DupeNukem/blob/main/samples/DupeNukem.CefSharp/ViewModels/MainWindowViewModel.cs))\n* [`Xamarin.Forms`(`Xam.Plugin.WebView`)](https://www.nuget.org/packages/Xam.Plugin.WebView). ([Fully sample code is here](https://github.com/kekyo/DupeNukem/blob/main/samples/DupeNukem.Xamarin.Forms/ViewModels/ContentPageViewModel.cs))\n* `.NET MAUI`. ([Fully sample code is here](https://github.com/kekyo/DupeNukem/blob/main/samples/DupeNukem.Maui/))\n\n----\n\n## Setup sequence\n\nSetup sequence is gluing between `WebView` and DupeNukem `WebViewMessenger`.\nDupeNukem uses only \"strings\" to exchange messages.\nIn the code example below (Edge WebView2 on WPF), Step 2 and Step 3 are also set up to mutually exchange message strings.\n\n(Another browser components maybe same as setup process.\nSee [Gluing browsers](#gluing-browsers) section below.)\n\nFirst time, you need to install [DupeNukem package from NuGet](https://www.nuget.org/packages/DupeNukem).\nThen write initial sequence:\n\n```csharp\n// Startup sequence.\n// Bound between Edge WebView2 and DupeNukem WebViewMessenger.\n\n// Step 1: Construct DupeNukem WebViewMessenger.\n// Default timeout duration is 30sec.\nvar messenger = new WebViewMessenger();\n\n//////////////////////////////////////////\n\n// Step 2: Hook up .NET --\u003e JavaScript message handler.\nmessenger.SendRequest += (s, e) =\u003e\n    webView2.CoreWebView2.PostWebMessageAsString(e.Message);\n\n// Step 3: Hook up JavaScript --\u003e .NET message handler.\nvar serializer = Messenger.GetDefaultJsonSerializer();\nwebView2.CoreWebView2.WebMessageReceived += (s, e) =\u003e\n{\n    if (serializer.Deserialize(\n        new StringReader(e.WebMessageAsJson),\n        typeof(object))?.ToString() is { } m)\n    {\n        messenger.ReceivedRequest(m);\n    }\n};\n\n// Step 4: Injected Messenger script.\nawait webView2.CoreWebView2.AddScriptToExecuteOnDocumentCreatedAsync(\n    messenger.GetInjectionScript().ToString());\n```\n\n----\n\n## Register methods/functions\n\nBulk register methods on an object:\n\n* Easy way, recommended.\n* All methods automatically inject proxy functions in JavaScript side.\n\n```csharp\n// Apply `CallableTarget` attribute on target callee method.\npublic class Calculator\n{\n    [CallableTarget]   // Automatic trimmed naming 'add'\n    public Task\u003cint\u003e AddAsync(int a, int b)\n    {\n        // ...\n    }\n\n    [CallableTarget(\"Subtract\")]   // Strictly naming\n    public Task\u003cint\u003e __Sub__123(int a, int b)\n    {\n        // ...\n    }\n}\n\n////////////////////////////////////////\n\n// JS: `const result = await dupeNukem.viewModels.calculator.add(1, 2);`\n// JS: `const result = await dupeNukem.viewModels.calculator.Subtract(1, 2);`\nvar calculator = new Calculator();\nmessenger.RegisterObject(calculator);\n\n// JS: `const result = await calc.add(1, 2);`\n// JS: `const result = await calc.Subtract(1, 2);`\nmessenger.RegisterObject(\"calc\", calculator);\n\n// JS: `const result = await add(1, 2);`         // (Put on `window.add`)\n// JS: `const result = await Subtract(1, 2);`    // (Put on `window.Subtract`)\nmessenger.RegisterObject(\"\", calculator);\n```\n\nRegister methods around .NET side:\n\n* Strict declarative each methods.\n\n```csharp\n// JS: `const result = await dupeNukem.viewModels.mainWindowViewModel.add`\nmessenger.RegisterFunc\u003cint, int, int\u003e(this.Add);\n// JS: `const result = await dupeNukem.viewModels.mainWindowViewModel.Subtract`\nmessenger.RegisterFunc\u003cint, int, int\u003e(this.__Sub__123);\n\n// Or, register directly delegate with method name.\n\n// JS: `const result = await dotnet_add(1, 2);`\nmessenger.RegisterFunc\u003cint, int, int\u003e(\n    \"dotnet_add\", (a, b) =\u003e Task.FromResult(a + b));\n// JS: `const result = await dotnet_sub(1, 2);`\nmessenger.RegisterFunc\u003cint, int, int\u003e(\n    \"dotnet_sub\", (a, b) =\u003e Task.FromResult(a - b));\n```\n\nDeclare functions around JavaScript side:\n\n```javascript\n// Global functions:\n\n// .NET: `var result = await messenger.InvokePeerMethodAsync(\"js_add\", 1, 2);`\nasync function js_add(a, b) {\n    return a + b;\n}\n\n// .NET: `var result = await messenger.InvokePeerMethodAsync(\"js_sub\", 1, 2);`\nasync function js_sub(a, b) {\n    return a - b;\n}\n\n// Member functions:\nclass Foo\n{\n    async add(a, b) {\n        return a + b;\n    }\n\n    async sub(a, b) {\n        return a - b;\n    }\n}\n\n// .NET: `var result = await messenger.InvokePeerMethodAsync(\"foo.add\", 1, 2);`\n// .NET: `var result = await messenger.InvokePeerMethodAsync(\"foo.sub\", 1, 2);`\nvar foo = new Foo();\n```\n\nNOTE: We have to put JavaScript object instance with `var` keyword.\nDupeNukem will fail invoking when use `const` or `let` keyword.\nIt is limitation for JavaScript specification.\n\n----\n\n## Callbacks\n\nDupeNukem can propagate callbacks passed as arguments.\nIn effect, it allows for bidirectional method and function calls:\n\n```csharp\n// .NET: Delegate callback functions to be passed to JS.\nstatic Task\u003cstring\u003e CallbackMethodAsync(int a, int b)\n{\n    return Task.FromResult($\"{a} and {b}\");\n}\n\n// (Result: \"Passed: 1 and 2\")\nvar result = await messenger.InvokePeerMethodAsync\u003cstring\u003e(\n    \"js_callback\", 1, 2, CallbackMethodAsync);`\n```\n\n``` javascript\n// JS: Call the .NET callback.\nasync function js_callback(a, b, cb) {\n    const str = await cb(a, b);\n    return \"Passed: \" + str;\n}\n```\n\nCallback calls in the opposite direction of the above are also possible:\n\n``` javascript\n// JS: Callback functions to be passed to .NET.\nvar result = await dotnet_callback(1, 2,\n    async (a, b) =\u003e {\n        return a + \" and \" + b;\n    });\n```\n\n```csharp\n// .NET: Call the JS callback.\npublic async Task\u003cstring\u003e dotnet_callback(\n    int a, int b, Func\u003cint, int, Task\u003cstring\u003e\u003e cb)\n{\n    var str = await cb(a, b);\n    return $\"Passed: {str}\";\n}\n```\n\nDelegates and functions can take up to 6 arguments.\nThe return value must be `Task` or `Promise` type.\nIt is possible to return the value of the result of asynchronous processing.\nThat is, `Task\u003cT\u003e` and `Promise\u003cT\u003e` are allowed.\n\nCallback delegates and functions that take `AbortSignal` type as\nan argument can also contain `AbortSignal`.\nIn that case, use that `AbortController` and `AbortSignal` for asynchronous processing cancellation of DupeNukem.\n\nCallback delegates and functions are automatically collected by the both garbage collectors\nwhen they are no longer referenced by anyone.\nTherefore, there is no need to think about managing these objects.\nYou can also store these objects somewhere when you receive them and call the callback when you need them.\n\n----\n\n## Exception\n\nDupeNukem can propagate exceptions to each other as exceptions without having to do anything.\n.NET and JavaScript, however, have different ways of expressing exceptions.\n\nWhen an exception is raised on .NET:\n\n```csharp\npublic Task foo()\n{\n    throw new ArgumentException(\"bar\");\n}\n```\n\n```javascript\ntry {\n    dotnet.foo();\n}\ncatch (e) {    // \u003c-- Error object\n    console.log(e.name + \": \" + e.message);\n    // console.log(e.detail)\n}\n```\n\nWhen an exception is raised on JavaScript:\n\n```javascript\nasync foo() {\n    throw new Error(\"foo\");\n}\n```\n\n```csharp\ntry\n{\n    await js.foo();\n}\ncatch (PeerInvocationException ex)\n{\n    Console.WriteLine(ex.Message);\n    // Console.WriteLine(ex.Detail);\n}\n```\n\nThe differences are shown below:\n\n* .NET exception class and does not create an exception object on the JavaScript side with the same name.\n  On the JavaScript side, `Error` object is always thrown.\n* On the JavaScript side, you can refer to the type name by `Error.name`.\n* You can refer to the message (`Exception.Message` property) by `Error.message`.\n* On the .NET side, an instance of the `PeerInvocationException` class is always thrown.\n* .NET exception stack traces are not combined by default on the JavaScript side.\n  Because .NET side and JavaScript side stack traces are different, so cannot be combined.\n  However, by setting `Messenger.SendExceptionWithStackTrace` to `true`,\n  .NET stack trace as a string to the JavaScript side.\n  This value is `false` by default, for safety reasons.\n  The stack trace is stored in `Error.detail`.\n* Similarly, the stack trace on the JavaScript side cannot be combined on the .NET.\n  Combined when be provided by the hosted JavaScript engine.\n\nAdditional information can be placed in the exception, but there are conditions for propagating this information:\n\n* Valid only for .NET exception classes.\n* The `ExceptionProperty` attribute must be applied.\n\n```csharp\npublic class FooException : Exception\n{\n    // Indicating additional information to the JavaScript side.\n    [ExceptionProperty]\n    public int StatusCode { get; }\n\n    public FooException(int statusCode, string message) :\n        base(message) =\u003e\n        this.StatusCode = statusCode;\n}\n```\n\n```javascript\ntry {\n    dotnet.foo();\n}\ncatch (e) {\n    console.log(e.props.statusCode);\n}\n```\n\nOn the JavaScript side, you can access `Error.props` as above to get the relevant additional information.\n\n----\n\n## Cancellation\n\n.NET has the `CancellationToken` type as the standard infrastructure for\nasynchronous processing.\n\nJavaScript has an `AbortSignal` that can be used to notify cancellation.\nDupeNukem automatically converts `AbortSignal` to `CancellationToken`.\n\n* `AbortSignal` is an ECMAScript standard type.\n  See [AbortSignal - MDN](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) for details.\n\n```javascript\n// Prepare a AbortController\nconst ac = new AbortController();\n\n// Setup canceler:\ndocument.getElementById(\"cancelButton\").onclick =\n    // Calls abort() function to signal cancelling.\n    () =\u003e ac.abort();\n\ntry {\n    // Invoke .NET method asynchronously:\n    const resut = await\n        dupeNukem.viewModels.mainWindowViewModel.\n        longAwaitedMethod(1, 2, ac.signal);   // \u003c-- Send AbortSignal object.\n}\ncatch (e) {\n    // An exception is thrown when a cancellation occurs.\n}\n```\n\n.NET implementation:\n\n```csharp\n[CallableTarget]\npublic async Task\u003cint\u003e LongAwaitedMethodAsync(\n    int a, int b, CancellationToken ct)   // \u003c-- Automatic conversion from AbortSignal.\n{\n    // Pass a CancellationToken to a time-consuming asynchronous process:\n    await Task.Delay(1000, ct);\n    return a + b;\n}\n```\n\nNOTE:\n\n* `AbortSignal` argument(s) can be defined anywhere in the argument set.\n  * Includes inside nested object fields.\n* The above example is a call in the JavaScript --\u003e .NET direction.\n  .NET --\u003e JavaScript direction calls are not yet allowed to use `CancellationToken` in 0.26.0.\n\n----\n\n## Obsoleted / Deprecated\n\nIn JavaScript --\u003e .NET method invoking, the following JavaScript debugging aids are available\nif the `Obsolete` attribute is applied to the .NET method.\n\nIf the normal `Obsolete` attribute is applied,\nthe following warning message will appear in the JavaScript console output:\n\n```csharp\n[CallableTarget]\n[Obsolete(\"This method will be obsoleted, switch to use `add_ng`.\")]\npublic static Task\u003cint\u003e AddAsync(int a, int b)\n{\n  // ...\n}\n```\n\n```\ncalc.add is obsoleted: This method will be obsoleted, switch to use `add_ng`.\n```\n\nAlso, if an error flag is applied to the `Obsolete` attribute,\nan exception will be thrown on the fly:\n\n```csharp\n[CallableTarget]\n[Obsolete(\"This method is obsoleted, have to switch `add_ng`.\", true)]\npublic static Task\u003cint\u003e AddAsync(int a, int b) =\u003e\n    // ...\n```\n\n```javascript\ntry {\n    consr r = await calc.add(1, 2);\n}\ncatch (e) {\n    // Raise error: calc.add is obsoleted: This method is obsoleted, have to switch `add_ng`.\n}\n```\n\nNote: This function is only available for proxy access,\nand will not work if called using the `invokeHostMethod()` function.\n\n----\n\n## Gluing browsers\n\nThere are examples for gluing sample code between your app and browser components.\n\n### Edge WebView2 (on WPF)\n\n```csharp\n// WebView2 webView2;\n\n// Step 2: Hook up .NET --\u003e JavaScript message handler.\nmessenger.SendRequest += (s, e) =\u003e\n    Dispatcher.CurrentDispatcher.Invoke(() =\u003e\n        webView2.CoreWebView2.PostWebMessageAsString(e.JsonString));\n\n// Step 3: Hook up JavaScript --\u003e .NET message handler.\nvar serializer = Messenger.GetDefaultJsonSerializer();\nwebView2.CoreWebView2.WebMessageReceived += (s, e) =\u003e\n{\n    if (serializer.Deserialize(\n        new StringReader(e.WebMessageAsJson),\n        typeof(object))?.ToString() is { } m)\n    {\n        messenger.ReceivedRequest(m);\n    }\n};\n\n// Step 4: Injected Messenger script.\nawait webView2.CoreWebView2.AddScriptToExecuteOnDocumentCreatedAsync(\n    messenger.GetInjectionScript().ToString());\n```\n\n### Edge WebView2 (on Windows Forms)\n\nThe only difference between Windows Forms and WPF is\nthe marshalling method to the main thread.\n\n```csharp\n// Step 2: Hook up .NET --\u003e JavaScript message handler.\nmessenger.SendRequest += (s, e) =\u003e\n    this.Invoke(() =\u003e\n        webView2.CoreWebView2.PostWebMessageAsString(e.JsonString));\n```\n\n### CefSharp (on WPF)\n\n```csharp\n// ChromiumWebBrowser cefSharp;\n\n// Step 2: Hook up .NET --\u003e JavaScript message handler.\nmessenger.SendRequest += (s, e) =\u003e\n    Dispatcher.CurrentDispatcher.Invoke(() =\u003e\n        cefSharp.BrowserCore.MainFrame.ExecuteJavaScriptAsync(\n            e.ToJavaScript()));\n\n// Step 3: Attached JavaScript --\u003e .NET message handler.\ncefSharp.JavascriptMessageReceived += (s, e) =\u003e\n    messenger.ReceivedRequest(e.Message.ToString());\n\n// Step 4: Injected Messenger script.\nvar script = messenger.GetInjectionScript();\ncefSharp.FrameLoadEnd += (s, e) =\u003e\n{\n    if (e.Frame.IsMain)\n    {\n        cefSharp.BrowserCore.MainFrame.ExecuteJavaScriptAsync(\n            script.ToString());\n    }\n};\n```\n\n### Xamarin Forms (Xam.Plugin.Webview)\n\nXamarin Forms provides a `WebView` control as a common basis for displaying a web browser.\nHowever, interoperating with JavaScript requires different implementations for each platform, such as Android and iOS.\nI assume that this is because the same web browsers are used, Chrome for Android and Safari for iOS.\n\nOne package that alleviates such cumbersome implementation is [Xam.Plugin.Webview project](https://github.com/SKLn-Rad/Xam.Plugin.Webview).\nHere is an example of using this package:\n\n```csharp\n// FormsWebView formsWebView;\n\n// Step 2: Hook up .NET --\u003e JavaScript message handler.\nmessenger.SendRequest += (s, e) =\u003e\n    Application.Current.Dispatcher.BeginInvokeOnMainThread(() =\u003e\n        formsWebView.InjectJavascriptAsync(e.ToJavaScript()));\n\n// Step 3: Attached JavaScript --\u003e .NET message handler.\nformsWebView.AddLocalCallback(\n    WebViewMessenger.PostMessageSymbolName,\n    messenger.ReceivedRequest);\n\n// Step 4: Injected Messenger script.\nvar script = messenger.GetInjectionScript();\nformsWebView.OnNavigationCompleted += (s, url) =\u003e\n    formsWebView.InjectJavascriptAsync(script.ToString());\n```\n\n### .NET MAUI\n\n.NET MAUI has a standard `WebView` control.\nHowever, this control lacks for sending messages from JavaScript to .NET.\n\nTherefore, you will need to implement these glue codes for each platform yourself.\nExamples for Windows and Android are placed in the sample code for your reference.\n\n* [.NET MAUI sample project](samples/DupeNukem.Maui/)\n\nThe following is a rough outline of the work required to achieve this:\n\n1. implement a `JavaScriptMultiplexedWebView` control derived from `WebView` that can receive messages from JavaScript.\n2. implement a platform specific handler `JavaScriptMultiplexedWebViewHandler` for the above control.\n3. Register the above handler at application startup.\n\nOnce these are in place, you can set up DupeNukem as follows:\n\n```csharp\n// Step 2: Hook up .NET --\u003e JavaScript message handler.\nmessenger.SendRequest += async (s, e) =\u003e\n{\n    // Marshal to main thread.\n    if (await UIThread.TryBind())\n    {\n        await webView.InvokeJavaScriptAsync(e.ToJavaScript());\n    }\n};\n\n// Step 3: Attached JavaScript --\u003e .NET message handler.\nwebView.MessageReceived += (s, e) =\u003e messenger.ReceivedRequest(e.Message);\n\n// Step 4: Injected Messenger script.\nvar script = messenger.GetInjectionScript(true);\nwebView.Navigated += (s, e) =\u003e\n{\n    if (e.Source is UrlWebViewSource eu \u0026\u0026\n        webView.Source is UrlWebViewSource wu \u0026\u0026\n        eu.Url == wu.Url)\n    {\n        webView.InvokeJavaScriptAsync(script.ToString());\n    }\n};\n```\n\n### Celenium WebDriver on .NET\n\nTODO: WIP\n\nIn the case of Celenium WebDriver, there is no standard way to notify message strings from the browser component to .NET side.\nIn this example (Step 3), the `alert()` function is used to notify a message strings.\n.NET side, the message is passed to DupeNukem when the alert occurs.\n\n```csharp\n// IWebDriver driver;\n\n// Step 2: Hook up .NET --\u003e JavaScript message handler.\nmessenger.SendRequest += (s, e) =\u003e\n    driver.ExecuteJavaScript(e.ToJavaScript());\n\n// Step 3: Attached JavaScript --\u003e .NET message handler.\nvar alert = wait.Until(ExpectedConditions.AlertIsPresent());\nmessenger.ReceivedRequest(alert.Text);\nalert.Accept();\n\n// Step 4: Injected Messenger script.\nvar script = messenger.GetInjectionScript();\ndriver.Navigated += (s, e) =\u003e\n{\n    if (e.Result == WebNavigationResult.Success \u0026\u0026\n        e.Url == webView.Source)\n    {\n        driver.ExecuteJavaScript(script.ToString());\n    }\n};\n```\n\n----\n\n## License\n\nApache-v2.\n\n----\n\n## History\n\n* 0.31.0:\n  * Improved stability for invoking with AbortSignals.\n* 0.30.0:\n  * Fixed base64 conversion problem.\n* 0.29.0:\n  * Renamed `JsonToken` to `JsonElement`, because VS intellisense is crashed by symbol confiction.\n  * Improved `JsonElement` conversions.\n* 0.28.0:\n  * Supported `JsonToken` type sets.\n    If you want to handle untyped JSON using the `JToken` type,\n    use it instead and the serialization will be correct.\n* 0.27.0:\n  * Supported serialization for JavaScript `ArrayBuffer`, `Uint8Array` and `Uint8ClampedArray` from/to .NET `byte[]`.\n  * Refactored object referencing handlers (In `AbortSignal` and function closures.)\n* 0.26.0:\n  * Added MAUI sample project.\n  * Switched cancellation object to `AbortSignal` ECMAScript standard object instead of `CancellationToken`.\n    * You can continue to use `CancellationToken` now, but marked obsoleted and will be removed in future release.\n  * Rolled back full-duplex cancellation infrastructure (in 0.23.0), because it is buggy.\n  * Replaced implementation on 0.22 branch based.\n* 0.25.0, 0.22.10:\n  * Fixed race condition when DupeNukem GC trimmer has arrived.\n* 0.24.0:\n  * Improved avoidance for another message processor confliction. #18\n  * Fixed causing duplicate OperationCancelledError symbol.\n  * Fixed ignoring closure discarder message.\n* 0.23.0:\n  * Re-implemented full-duplex cancellation infrastructure.\n* 0.22.0:\n  * Supported callback delegates/functions on the arguments.\n* 0.21.0:\n  * Added `ExceptionProperty` attribute.\n* 0.20.0:\n  * Removed obsoleted fragments.\n* 0.19.0:\n  * Changed `PeerInvocationException` instead of `JavaScriptException`.\n  * Added some deconstructor for entity classes.\n* 0.18.0:\n  * Added trim 'Async' from method name feature. \n* 0.17.2:\n  * Exposed control message interface on core library.\n* 0.17.1:\n  * Adjusted more signature and scope attributes.\n* 0.17.0:\n  * Implemented `IMessenger` neutral interface. Please fix indicating at obsolete warnings:\n    * `InvokeClientFunctionAsync(...)` ==\u003e `InvokePeerMethodAsync(...)`\n  * Fixed some method signature type nullability.\n* 0.16.0:\n  * Splitted core library into new `DupeNukem.Core` package, because need to be usable pure interoperation infrastructure. Please fix indicating at obsolete warnings:\n    * `new Messenger(...)` ==\u003e `new WebViewMessenger(...)`\n    * `JavaScriptTargetAttribute` ==\u003e `CallableTargetAttribute`\n  * Fixed failing to notify caught exception on JavaScript side before promise context.\n  * Changed sample Edge WebView2 gluing code.\n* 0.15.0:\n  * Upgraded `Newtonsoft.Json` to 13.0.1 (See [Vulnerability: Improper Handling of Exceptional Conditions in Newtonsoft.Json - GitHub Advisory Database](https://github.com/advisories/GHSA-5crp-9r3c-p9vr))\n* 0.14.0:\n  * Fixed causing duplicated key exception when derived class has same named overrided expose method.\n* 0.13.0:\n  * Changed showing trace message instead raise exception when SendRequest aren't hooked.\n* 0.12.0:\n  * Unhooked any events on calling Dispose method. (avoid memory leaks)\n* 0.11.0:\n  * Help debugging by warning log and raise exception at JavaScript when , .NET method is marked with `Obsolete` attribute.\n  * Fixed registering implicit proxy methods at around browser reloading. \n* 0.10.0:\n  * Supported `CancellationToken` when JavaScript --\u003e .NET direction calling.\n* 0.9.0:\n  * Fixed didn't initialize on XF iOS.\n* 0.8.0:\n  * Fixed causing InvalidMethodName exception when use longer method name.\n* 0.7.0:\n  * Supported CefSharp and Xamarin Forms.\n* 0.6.0:\n  * Supported proxy object on JavaScript side.\n  * Implemented automatic thread marshaling (No need for marshalling to UI threads as manually.)\n* 0.5.0:\n  * Supported customize json format with `JsonSerializer` and made defaults with camel-casing serialization.\n  * Made defaults for all symbol naming to camel case.\n  * Added more target platforms.\n* 0.4.0:\n  * New bulk register methods on an object by `RegisterObject(obj)` method.\n  * Fixed invoking silent result with invalid method name.\n* 0.3.0:\n  * Implemented flexible argument type handling.\n* 0.2.0:\n  * Improved enum type handling.\n* 0.1.0:\n  * Initial release.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkekyo%2Fdupenukem","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkekyo%2Fdupenukem","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkekyo%2Fdupenukem/lists"}