How do I await the result of EvaluateJavascript? - javascript

I have a CustomWebViewRenderer for Android that contains an event to process javascript using EvaluateJavascript, and I have a Callback object to catch the result of the javascript, but I need to send that result back up the chain to the initial calling function. Right now OnRunJavascript completes before OnRecieveValue runs, so e.Result is not set properly.
public void OnRunJavascript(object sender, JavascriptEventArgs e)
{
if (Control != null)
{
var jsr = new JavascriptResult();
Control.EvaluateJavascript(string.Format("javascript: {0}", e.Script), jsr);
e.Result = jsr.Result;
}
}
public class JavascriptResult : Java.Lang.Object, IValueCallback
{
public string Result;
public void OnReceiveValue(Java.Lang.Object result)
{
string json = ((Java.Lang.String)result).ToString();
Result = Newtonsoft.Json.JsonConvert.DeserializeObject<string>(json);
Notify();
}
}

One option is to use a TaskCompletionSource with async/await. I like this because it's simple (relatively little code), and let's me quickly turn synchronous code into something that looks like async code.
Using your example, I will add in a TaskCompletionSource and create a Task which can be used with await later on in your program.
public void OnRunJavascript(object sender, JavascriptEventArgs e)
{
if (Control != null)
{
var jsr = new JavascriptResult();
Control.EvaluateJavascript(string.Format("javascript: {0}", e.Script), jsr);
// TODO await jsr.CompletionTask
e.Result = jsr.Result;
}
}
public class JavascriptResult : Java.Lang.Object, IValueCallback
{
public string Result;
public Task CompletionTask {get { return jsCompletionSource.Task; } }
private TaskCompletionSource<bool> jsCompletionSource = new TaskCompletionSource<bool>();
public void OnReceiveValue(Java.Lang.Object result)
{
string json = ((Java.Lang.String)result).ToString();
Result = Newtonsoft.Json.JsonConvert.DeserializeObject<string>(json);
Notify();
jsCompletionSource.SetResult(true); // completes the Task
// the await will finish
}
}
Notice the TODO inside OnRunJavascript which, I believe, is what you're looking to accomplish. That Task can be passed somewhere else to be awaited and then access the JavascriptResult.
I hope that helps.

Related

Return string from Javascript to razor page using JSInterOp

How I return string(tmppath) from index.js file and set it in result variable of OnAfterRenderAsync. I would like the below #result variable to hold the result string
Index.razor:
<p>#result</p> // result should be displayed here
#code {
private string? result;
private DotNetObjectReference<FetchData>? objRef;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
objRef = DotNetObjectReference.Create(this);
result = await JSRuntime.InvokeAsync<string>("Create", objRef); }
}
[JSInvokable]
public string GetMessage(string passedName) => $"Hello, {passedName}!";
public void Dispose()
{
objRef?.Dispose();
}
Problem is first Create function in index.js returns the string "SomeString" to Razor page. But when I use second Create function, it does not return tmppath to razor page. May I know where I am going wrong. Thank you.
Index.js:
function Create(dotNetHelper) {
var str= "SomeString"
return dotNetHelper.invokeMethodAsync('GetMessage', str);
}
function Create(dotNetHelper) {
$('#file').change(function (event) {
//var tmppath= URL.createObjectURL(event.target.files[0]);
var tmppath= "blob:https://localhost:44302/784a5c06-c647-432c-b62d-74067f9ddddd"
console.log(tmppath); // prints in console
return dotNetHelper.invokeMethodAsync('GetMessage', tmppath);
});
}
You need to adapt your code from the Microsoft example because your Create function will not return the result because Create sets an event handler.
You need to set result in your [JSInvokable], do not go back to JS.
[JSInvokable]
public void SetResult(string path)
{
result = path;
StateHasChanged();
}
No need to return anything in the change event handler:
function Create(dotNetHelper) {
$('#file').change(function (event) {
//var tmppath= URL.createObjectURL(event.target.files[0]);
var tmppath= "blob:https://localhost:44302/784a5c06-c647-432c-b62d-74067f9ddddd"
console.log(tmppath); // prints in console
dotNetHelper.invokeMethodAsync('SetResult', tmppath);
});
}
There is no result to get from await JSRuntime.InvokeAsync<string>:
protected override async Task OnAfterRenderAsync(bool firstRender)
{
objRef = DotNetObjectReference.Create(this);
await JSRuntime.InvokeVoidAsync("Create", objRef);
}
You still should make sure you don't duplicate the change event handlers on every render, however, you might not see its effect if you don't.

Execute Javascript on external url using HybridView, Xamarin cross platform

I have tried to execute JavaScript on an external url (ie: http://facebook.com) using WebView from Visual Studio Mac 2019, and so far no results.
To do so, I have tried to follow along with the official tutorial here https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/custom-renderer/hybridwebview, and also tried a simpler one here: https://xamarinhelp.com/xamarin-forms-webview-executing-javascript/
Here is what I did with explanations:
On my shared folder, I created an HybridWebView class with the following code:
public class HybridWebView : WebView
{
Action<string> action;
public static readonly BindableProperty UriProperty = BindableProperty.Create(
propertyName: "Uri",
returnType: typeof(Func<string, Task<string>>),
declaringType: typeof(HybridWebView),
defaultValue: default(string));
public string Uri
{
get => (string)GetValue(UriProperty);
set
{
SetValue(UriProperty, value);
}
}
public void RegisterAction(Action<string> callback)
{
action = callback;
}
public void Cleanup()
{
action = null;
}
public void InvokeAction(string data)
{
if (action == null || data == null)
{
return;
}
action.Invoke(data);
}
public Func<string, Task<string>> ExecuteJavascript
{
get { return (Func<string, Task<string>>)GetValue(UriProperty); }
set { SetValue(UriProperty, value); }
}
}
From The macOS project which I use to test my cross-platform app, I tried the following custom renderer:
public class HybridWebViewRenderer : ViewRenderer<HybridWebView, WKWebView>
{
protected override void OnElementChanged(ElementChangedEventArgs<HybridWebView> e)
{
base.OnElementChanged(e);
var webView = e.NewElement as HybridWebView;
if (webView != null)
{
Control.LoadRequest(new NSUrlRequest(new NSUrl(Element.ExecuteJavascript.ToString())));
}
}
}
To note that the following part wouldn't work:
var webView = e.NewElement as HybridWebView;
if (webView != null)
webView.ExecuteJavascript = (js) =>
{
return Task.FromResult(this.ExecuteJavascript(js)); // issue at ExecuteJavascript with following error ('HybridWebViewRenderer' does not contain a definition for 'ExecuteJavascript' ), hence replaced by Control.LoadRequest ...
};
From my ViewModel, I did the following:
public Func<string, Task<string>> EvaluateJavascript { get; set; }
public async Task OnConnectTapped()
{
Console.WriteLine("on connect tapped");
// passing the url onto a connection service
var hybridWebView = new HybridWebView
{
Uri = "https://facebook.com/"
};
//hybridWebView.InvokeAction("document.getElementById('td');");
//var result = await hybridWebView.RegisterAction(data => DisplayAlert("Alert", "Hello " + data, "OK"));
var result = await hybridWebView.ExecuteJavascript("document.cookie;");
Console.WriteLine("result is {0}", result);
}
Here is the error when trying to execute my code:
System.NullReferenceException: Object reference not set to an instance of an object
at MyApp.ViewModel.MainModel.OnConnectTapped () [0x00031] in .../../././/ViewModel/MainModel.cs:451
at .......<.ctor>g__c5|48_9 () [0x0001f] in /../../../.cs:143
at System.Runtime.CompilerServices.AsyncMethodBuilderCore+<>c.<ThrowAsync>b__7_0 (System.Object state) [0x00000] in /Users/builder/jenkins/workspace/xamarin-macios/xamarin-macios/external/mono/mcs/class/referencesource/mscorlib/system/runtime/compilerservices/AsyncMethodBuilder.cs:1021
at Foundation.NSAsyncSynchronizationContextDispatcher.Apply () [0x00002] in /Library/Frameworks/Xamarin.Mac.framework/Versions/6.6.0.12/src/Xamarin.Mac/Foundation/NSAction.cs:178
at at (wrapper managed-to-native) AppKit.NSApplication.NSApplicationMain(int,string[])
at AppKit.NSApplication.Main (System.String[] args) [0x00040] in /Library/Frameworks/Xamarin.Mac.framework/Versions/6.6.0.12/src/Xamarin.Mac/AppKit/NSApplication.cs:100
at redacted.macOS.MainClass.Main (System.String[] args) [0x00017] in /Users/dom-bruise/Projects/redacted/redacted.macOS/Main.cs:11
For me, it could either be because I can't execute external pages, or the part where I replaced by the following messing up my attempt.
if (webView != null)
{
Control.LoadRequest(new NSUrlRequest(new NSUrl(Element.ExecuteJavascript.ToString())));
}
My main goal here is to have my app execute JavaScript underneath the hood on pages using WebView, and fill in forms automatically calling back C# from my app.

Getting 400 response error code when running query with apollo/react-hooks [duplicate]

I am using this endpoint:
#PostMapping("graphql")
public ResponseEntity<Object> getResource(#RequestBody Object query) { // String query
ExecutionResult result;
if (query instanceof String) {
result = graphQL.execute(query.toString()); // if plain text
} else{
String queryString = ((HashMap) query).get("query").toString();
Object variables = ((HashMap) query).get("variables");
ExecutionInput input = ExecutionInput.newExecutionInput()
.query(queryString)
.variables((Map<String, Object>) variables) // "var1" -> "test1"
.build();
result = graphQL.execute(input);
}
return new ResponseEntity<Object>(result, HttpStatus.OK);
}
When i don't have variable it works fine:
query {
getItem(dictionaryType: "test1") {
code
name
description
}
}
When i add variable it starts to fail, see here:
query {
getItem(dictionaryType: $var1) {
code
name
description
}
}
In my schema i have defined the query section as followed:
type Query {
getItem(dictionaryType: String): TestEntity
}
In java code:
#Value("classpath:test.graphqls")
private Resource schemaResource;
private GraphQL graphQL;
#PostConstruct
private void loadSchema() throws IOException {
File schemaFile = schemaResource.getFile();
TypeDefinitionRegistry registry = new SchemaParser().parse(schemaFile);
RuntimeWiring wiring = buildWiring();
GraphQLSchema schema = new SchemaGenerator().makeExecutableSchema(registry, wiring);
graphQL = GraphQL.newGraphQL(schema).build();
}
private RuntimeWiring buildWiring() {
initializeFetchers();
return RuntimeWiring.newRuntimeWiring()
.type("Query", typeWriting -> typeWriting
.dataFetcher("getItem", dictionaryItemFetcher)
)
.build();
}
private void initializeFetchers() {
dictionaryItemFetcher = dataFetchingEnvironment ->
dictionaryService.getDictionaryItemsFirstAsString(dataFetchingEnvironment.getArgument("dictionaryType"));
}
Any variables used inside an operation must be declared as part of the operation definition, like this:
query OptionalButRecommendedQueryName ($var1: String) {
getItem(dictionaryType: $var1) {
code
name
description
}
}
This allows GraphQL to validate your variables against the provided type, and also validate that the variables are being used in place of the right inputs.

Is it possible to return the result of a JS confirmation dialog presented by JXBrowser back to the JS section that called it?

I'm using JavaFX/JXBrowser to show an alert/dialog when the web page loaded into the Browser calls on Window.alert or window.confirm. However, I can't figure out how to return the result of the confirmation dialog (true/false) to JS. Since alert.showAndWait() is a blocking function, JS should wait for this result. However, showAndWait is also called in a Platform.runLater runnable, so I can't return the result. Short of writing JS functions to do the true/false code and calling those based on the result of showAndWait, is there any other option?
browser.setDialogHandler(new DialogHandler() {
#Override
public CloseStatus onConfirmation(DialogParams params) {
Platform.runLater(new Runnable() {
#Override
public void run() {
Alert alert = new Alert(AlertType.CONFIRMATION);
alert.setTitle("Yes/No");
alert.setHeaderText(null);
alert.setContentText("Yes/No");
Optional<ButtonType> result = alert.showAndWait();
if(result.isPresent())
{
if(result.get()==ButtonType.YES)
{
//Send true to calling JS function
}else
{
//Send false to calling JS function
}
}else
{
System.out.println("No result!");
}
}
});
return null; //because I need to return something and I can't figure out how to get values from the runnable
}
...
}
You can use the following approach:
#Override
public CloseStatus onConfirmation(DialogParams params) {
final AtomicReference<CloseStatus> status = new AtomicReference<CloseStatus>();
final CountDownLatch latch = new CountDownLatch(1);
Platform.runLater(new Runnable() {
#Override
public void run() {
Alert alert = new Alert(AlertType.CONFIRMATION);
alert.setTitle("Yes/No");
alert.setHeaderText(null);
alert.setContentText("Yes/No");
Optional<ButtonType> result = alert.showAndWait();
if (result.isPresent()) {
if (result.get() == ButtonType.YES) {
status.set(CloseStatus.OK);
} else {
status.set(CloseStatus.CANCEL);
}
} else {
System.out.println("No result!");
}
}
});
try {
latch.await();
} catch (InterruptedException ignored) {
Thread.currentThread().interrupt();
}
return status.get();
}

Consume Server Sent in C#

I am using ServiceStack.Client to consume, the data pushed by my server(which is an aspx page).
Below is the code which i use to consume the data using ServiceStack Client:
using System;
using System.Net.Sockets;
using System.Net;
using System.Security.Cryptography;
using System.Threading;
using ServiceStack;
using System.Collections.Generic;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
ServerEventConnect connectMsg = null;
var msgs = new List<ServerEventMessage>();
var commands = new List<ServerEventMessage>();
var errors = new List<Exception>();
var client = new ServerEventsClient("https://testing.leadsquared.com/ReferralCampaign/Demo")
{
OnConnect = e => PrintMsg(e),
OnCommand = e => PrintCmdMsg(e),
OnMessage = e => PrintCmMsg(e),
OnException = e => PrintExMsg(e)
}.Start();
Console.Read();
}
private static void PrintCmMsg(ServerEventMessage e)
{
if (e != null)
{
PrintMsg(e.Data);
}
}
private static void PrintExMsg(Exception e)
{
if (e != null)
{
PrintMsg(e.Message);
}
}
private static void PrintCmdMsg(ServerEventMessage e)
{
if (e != null)
{
PrintMsg(e.Data);
}
}
private static void PrintMsg(ServerEventConnect e)
{
if (e!=null)
{
PrintMsg(e.Data);
}
}
private static void PrintMsg(string x)
{
Console.WriteLine(x);
}
}
}
When I run my code , the client does print any message on the console.
The ConnectionDisplayName property is "(not connected)".
If i subscribe to the same URL using javascript EventSource, i get the notifications.
My requirement is that I would want to consume the data by my server in C#.
How can I achieve this?
Firstly the url needs to be the BaseUri where ServiceStack is hosted, i.e. the same url used in JavaScript ServerEvents Client, e.g:
var client = new ServerEventsClient(BaseUrl).Start();
It's not clear if /ReferralCampaign/Demo is the BaseUri or not.
You will also want to call Connect() to wait for the client to make a connection, e.g:
await client.Connect();
Then to see message events you'll need to call a ServiceStack Service that publishes a Notify* Event on IServerEvents API which you can use with a separate JsonServiceClient or the ServiceClient available in ServerEventsClient, e.g:
client.ServiceClient.Post(new PostRawToChannel {
From = client.SubscriptionId,
Message = "Test Message",
Channel = channel ?? "*",
Selector = "cmd.announce",
});
This is an example calling the Chat PostRawToChannel ServiceStack Service:
public class ServerEventsServices : Service
{
public IServerEvents ServerEvents { get; set; }
public void Any(PostRawToChannel request)
{
// Ensure the subscription sending this notification is still active
var sub = ServerEvents.GetSubscriptionInfo(request.From);
if (sub == null)
throw HttpError.NotFound("Subscription {0} does not exist".Fmt(request.From));
// Check to see if this is a private message to a specific user
if (request.ToUserId != null)
{
// Only notify that specific user
ServerEvents.NotifyUserId(request.ToUserId, request.Selector, request.Message);
}
else
{
// Notify everyone in the channel for public messages
ServerEvents.NotifyChannel(request.Channel, request.Selector, request.Message);
}
}
}
I also recommend looking at the C# ServerEventTests for complete stand-alone examples using C# ServerEventClient.

Categories