B4J Question BANano Oauth2 authentication (SOLVED)

walterf25

Expert
Licensed User
Longtime User
Hi all, has anyone attempted and been successful signing in to any google cloud APIs using OAuth2, I need to use Google Cloud Datastore Rest API but in order to access any of the data stored in a nosql database I need to first authenticate the users, I know this is possible in B4J, B4A and B4i, but is it possible with B4J And BANano, if so how would I go about doing this?

I am not very familiar with everything in BANano but I'm sure there must be a way, i don't know if some of the B4J libraries can be used with BANano.

Any thoughts?

Walter
 

alwaysbusy

Expert
Licensed User
Longtime User
if some of the B4J libraries can be used with BANano
They can not in pure BANano (the Browser side). They can on the server side (BANanoServer project).

I assume this has something to do with your other node.js question? I'll try to get something setup on my machine on Monday so I can try if BANano does work with node.js.

Alwaysbusy
 
Upvote 0

walterf25

Expert
Licensed User
Longtime User
They can not in pure BANano (the Browser side). They can on the server side (BANanoServer project).

I assume this has something to do with your other node.js question? I'll try to get something setup on my machine on Monday so I can try if BANano does work with node.js.

Alwaysbusy
Hey Ilan, thanks, will really appreciate it if you could take a look, i know is a lot to ask but seems to me we are lacking this type of functionality, personally i am not very familiar with either javascript or web apps, I have a need to build something at work, i have been leveraging the power of B4x altogether here at work so far everyone is very amazed at how much can be accomplished with it, would hate to let my team down and not be able to complete this task.

Let me know if you need me to do any testing.

Again thanks, really appreciate your contribution to the community.

Walter
 
Upvote 0

fredo

Well-Known Member
Licensed User
Longtime User
...google cloud APIs...

If I understand it correctly, there is a database in your organization that needs to be accessed via REST API in a platform-independent way.

I assume you want to use Node.js, since there are some examples available in SO for your use case.

But there is also a JavaScript client library, with which the Google APIs are accessible:

Google API Client Library for JavaScript https://github.com/google/google-api-javascript-client


We use asynchronous calls to minimize the impact of latency. In this respect, your JavaScript endpoint must be able to communicate asynchronously.
This should be possible with BANano (please correct me, @alwaysbusy, if I'm wrong).:


[BANano] [SOLVED] What is the right way to use BANano.WaitFor? https://www.b4x.com/android/forum/t...s-the-right-way-to-use-banano-waitfor.127285/


The hurdle for accessing Google API data can still be influenced by choosing the right authentication. In addition to the API key method, there is also the option of using an access token via service account.

If your use case allows it, Cloud Datastore can be upgraded to Firestore and run in Datastore mode (Firestore is the next generation of Datastore).
This opens up the possibility to take full advantage of Firebase authentications:


Firebase Authentication https://firebase.google.com/docs/auth


BTW here is a sample code for the "Google Compute Engine API", which could be usable for your case:

Google Compute Engine JavaScript Sample Application: https://github.com/GoogleCloudPlatform/compute-getting-started-javascript
 
Upvote 0

walterf25

Expert
Licensed User
Longtime User
If I understand it correctly, there is a database in your organization that needs to be accessed via REST API in a platform-independent way.

I assume you want to use Node.js, since there are some examples available in SO for your use case.

But there is also a JavaScript client library, with which the Google APIs are accessible:

Google API Client Library for JavaScript https://github.com/google/google-api-javascript-client


We use asynchronous calls to minimize the impact of latency. In this respect, your JavaScript endpoint must be able to communicate asynchronously.
This should be possible with BANano (please correct me, @alwaysbusy, if I'm wrong).:


[BANano] [SOLVED] What is the right way to use BANano.WaitFor? https://www.b4x.com/android/forum/t...s-the-right-way-to-use-banano-waitfor.127285/


The hurdle for accessing Google API data can still be influenced by choosing the right authentication. In addition to the API key method, there is also the option of using an access token via service account.

If your use case allows it, Cloud Datastore can be upgraded to Firestore and run in Datastore mode (Firestore is the next generation of Datastore).
This opens up the possibility to take full advantage of Firebase authentications:


Firebase Authentication https://firebase.google.com/docs/auth


BTW here is a sample code for the "Google Compute Engine API", which could be usable for your case:

Google Compute Engine JavaScript Sample Application: https://github.com/GoogleCloudPlatform/compute-getting-started-javascript
Just a quick update for those following this post, based on the information provided by @fredo I was able to do the following:
https://github.com/google/google-ap...md#authorizing-and-making-authorized-requests
Based on this example in JavaScript I can get the browser to pop up the signing window where you would choose the profile email address you would like to use to login.

At the moment that's all I can do as I need to set up a server on my pc to allow my Company's security protocols to allow me to completely sign in as my PC needs to be whitelisted on their end.

Here's the javascript code i have so far which seems to work fine.
JavaScript:
#If JAVASCRIPT
function loadlib(){
    console.log("loaded client libs");
    gapi.load('client:auth2', initClient);
}
    
    var apiKey = 'xxxxxxxxx40mIGihqJ98kyLpxxxxxxxxxxxx';
    var clientId = '875655xxxxxxxxxxxx.apps.googleusercontent.com';
    var discoveryDocs = ["https://datastore.googleapis.com/$discovery/rest?version=v1"];
    var scopes = 'https://www.googleapis.com/auth/datastore';
    
function initClient() {
        console.log("apiKey: " + apiKey);
        console.log("clientId: " + clientId);
        console.log("scopes: " + scopes);
        gapi.client.init({
            apiKey: apiKey,
            discoveryDocs: discoveryDocs,
            clientId: clientId,
            scope: scopes
        }).then(function () {
          // Listen for sign-in state changes.
          gapi.auth2.getAuthInstance().isSignedIn.listen(updateSigninStatus);
          console.log("isSignedIn: " + gapi.auth2.getAuthInstance().isSignedIn);
          // Handle the initial sign-in state.
          updateSigninStatus(gapi.auth2.getAuthInstance().isSignedIn.get());
        
          //authorizeButton.onclick = handleAuthClick;
          //signoutButton.onclick = handleSignoutClick;
        });
      }
      
      function updateSigninStatus(isSignedIn) {
        if (isSignedIn) {
          //authorizeButton.style.display = 'none';
         // signoutButton.style.display = 'block';
         // makeApiCall();
         console.log("isSignedIn: " + isSignedIn);
        } else {
          //authorizeButton.style.display = 'block';
         // signoutButton.style.display = 'none';
         console.log("notSignedIn: " + isSignedIn);
        }
}

function SignIn(){
    console.log("signing in");
     gapi.auth2.getAuthInstance().signIn();
}
#End If

Inside of the AppStart Function I load the following javascript files:
B4X:
    BANano.Header.AddJavascriptFile("https://apis.google.com/js/client.js")
    BANano.Header.AddJavascriptFile("https://apis.google.com/js/api.js")

And inside the BANano_Ready function
B4X:
Sub BANano_Ready()
    Dim body As BANanoElement = BANano.GetElement("#body")
    body.Append("Hello Walter")
    BANano.RunInlineJavascriptMethod("loadlib", Null)
    BANano.Sleep(500)
    BANano.RunInlineJavascriptMethod("SignIn", Null)
End Sub

As I mentioned this seems to work fine, it pops up the sign in window, but in order for me to test this completely I need to set up a local server, does anyone have any experience on doing this, if so can you guys provide some tips.

Thanks,
Walter
 
Upvote 0

walterf25

Expert
Licensed User
Longtime User
Another quick update, i am not able to authenticate using the code used in the post above.

I went ahead and added the following code to retrieve data stored in the datastore database.
JavaScript:
  function execute(Kind, ID) {
    return gapi.client.datastore.projects.lookup({
      "projectId": "snaplab-993",
      "resource": {
        "readOptions": {
          "readConsistency": "EVENTUAL"
        },
        "keys": [
          {
            "path": [
              {
                "kind": Kind,
                "id": ID
              }
            ],
            "partitionId": {
              "namespaceId": "current_drain_data",
              "projectId": "snaplab-993"
            }
          }
        ]
      }
    })
        .then(function(response) {
                // Handle the results here (response.result has the parsed body).
                console.log("Response", response);
                
              },
              function(err) { console.error("Execute error", err); });
        return response;
  }

And I call this function with the following B4J code.
B4X:
BANano.RunInlineJavascriptMethod("execute", Array ("waveform", 5633571828006912))

This works well and I get the expected response, now my question is, how can I catch the response and iterate through each value in the json object?
The response looks like this:
JSON:
Response {
  "found": [
    {
      "entity": {
        "key": {
          "partitionId": {
            "projectId": "snaplab-993",
            "namespaceId": "current_drain_data"
          },
          "path": [
            {
              "kind": "waveform",
              "id": "5633571828006912"
            }
          ]
        },
        "properties": {
          "ch0": {
            "arrayValue": {
              "values": [
                {
                  "doubleValue": 59.961688893613768,
                  "excludeFromIndexes": true
                },
                {
                  "doubleValue": 49.6029332019204,
                  "excludeFromIndexes": true
                },
                {
                  "doubleValue": 51.303013130144294,
                  "excludeFromIndexes": true
                },
                {
                  "doubleValue": 61.339663028985122,
                  "excludeFromIndexes": true
                },
                {
                  "doubleValue": 56.875304507789565,
                  "excludeFromIndexes": true
                },
                {
                  "doubleValue": 58.992594740979818,
                  "excludeFromIndexes": true
                },
                {
                  "doubleValue": 57.622256734318327,
                  "excludeFromIndexes": true
                },

I am only interested in retrieving the ch0 values which are of double types, can someone enlighten me on how to accomplish get pass the response to BANano object and retrieve the correct values?

Thanks,
Walter
 
Upvote 0

alwaysbusy

Expert
Licensed User
Longtime User
EDIT: modfied to fix the below errors.

This is how I would try it:
In globals:
B4X:
Dim gapi As BANanoObject

somewhere in BANano_ready()
B4X:
LoadLib

This would be the LoadLib methods needed
B4X:
Sub LoadLib()
    Log("loaded client libs")
    gapi.Initialize("gapi")
    gapi.RunMethod("load", Array("client:auth2", BANano.CallBack(Me, "InitClient", Null)))
End Sub

Sub InitClient() 'ignore
    Dim Params As Map
    Params.Initialize
    Params.Put("apiKey", "AIzaSyCAjcxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")
    Params.Put("discoveryDocs", Array("https://datastore.googleapis.com/$discovery/rest?version=v1"))
    Params.Put("clientId", "28675018xxxxxxxxxxxxxxxxxxxxxxxxxxxxge.apps.googleusercontent.com")
    Params.Put("scope", "https://www.googleapis.com/auth/datastore")
 
    BANano.Await(gapi.GetField("client").RunMethod("init", Params))
 
    Log(gapi.GetField("auth2").RunMethod("getAuthInstance", Null).GetField("isSignedIn").RunMethod("get", Null))
End Sub

Then making your request to get stuff from the datastore (here I got to improvise a bit as I can not really access your datastore, but I help with it once we got there).
B4X:
Private Sub SKButton1_Click (event As BANanoEvent)
    Dim Projects As BANanoObject = gapi.GetField("client").GetField("datastore").GetField("projects")
 
    Dim Kind as String = "waveform"
    Dim ID as long = 1234567890123
    Dim Params As String = $"
    {
"projectId": "snaplab-993",
"resource": {
"readOptions": {
"readConsistency": "EVENTUAL"
},
"keys": [
{
"path": [
{
                "kind": "${Kind}",
                "id": ${ID}
              }
            ],
            "partitionId": {
              "namespaceId": "current_drain_data",
              "projectId": "snaplab-993"
            }
          }
        ]
      }
    }
    "$
   
   ' make an object from the string
    Params = BANano.FromJson(Params)
    
    Log(Params)

    Dim JsonObject As BANanoObject
    Dim JsonStr as String

    Dim prom As BANanoPromise = Projects.RunMethod("lookup", Params) 
    prom.then(JsonObject) 
        Log(JsonObject)
        Return JsonObject.Body ' resolve the body part of the response stream
    prom.then(JsonStr)       
        Dim newObj as BANanoObject = BANano.FromJson(jsonStr) ' let(s make a javascript json object from it

        Dim founds() As BANanoObject = newObj.GetField("found") ' <----
        Log(founds)
        Dim values As List = founds(0).GetField("entity").GetField("properties").GetField("ch0").GetField("arrayValue").GetField("values")
        For i = 0 To values.Size - 1
            Dim m As Map = values.Get(i)
            Log(m.Get("doubleValue"))
        Next
    prom.end
End sub

Alwaysbusy
 
Last edited:
Upvote 0

walterf25

Expert
Licensed User
Longtime User
#if javascript _params = { "projectId": "snaplab-993", "resource": { "readOptions": { "readConsistency": "EVENTUAL" }, "keys": [ { "path": [ { "kind": _kind, "id": _id } ], "partitionId": { "namespaceId": "current_drain_data", "projectId": "snaplab-993" } } ] } } #End If
I am getting the following error:
app.js:13 Uncaught (in promise) ReferenceError: _kind is not defined
at banano_waveformanalyzer.loaddata (app.js:13)
at banano_waveformanalyzer.initclient (app.js:3)

I have Dim Kind as String right before the javascript part.

Any thoughts?

Walter
 
Upvote 0

alwaysbusy

Expert
Licensed User
Longtime User
Can you PM me your app.js file? But I'm just thinking that my shortcut won't work in release mode anyway as the variables will be renamed to short ones.

Try this one instead:
B4X:
Dim Params As String = $"
    {
      "projectId": "snaplab-993",
      "resource": {
        "readOptions": {
          "readConsistency": "EVENTUAL"
        },
        "keys": [
          {
            "path": [
              {
                "kind": "${Kind}",
                "id": ${ID}
              }
            ],
            "partitionId": {
              "namespaceId": "current_drain_data",
              "projectId": "snaplab-993"
            }
          }
        ]
      }
    }
    "$
    
   ' make an object from the string
    Params = BANano.FromJson(Params)
    
    log(Params)

    ...

Alwaysbusy
 
Upvote 0

walterf25

Expert
Licensed User
Longtime User
Can you PM me your app.js file? But I'm just thinking that my shortcut won't work in release mode anyway as the variables will be renamed to short ones.

Try this one instead:
B4X:
Dim Params As String = $"
    {
      "projectId": "snaplab-993",
      "resource": {
        "readOptions": {
          "readConsistency": "EVENTUAL"
        },
        "keys": [
          {
            "path": [
              {
                "kind": "${Kind}",
                "id": ${ID}
              }
            ],
            "partitionId": {
              "namespaceId": "current_drain_data",
              "projectId": "snaplab-993"
            }
          }
        ]
      }
    }
    "$
   
   ' make an object from the string
    Params = BANano.FromJson(Params)
   
    log(Params)

    ...

Alwaysbusy
That worked, but i am now getting another error:

cb=gapi.loaded_0:981 Uncaught (in promise) mB {message: "Ma`projectId", wK: true, stack: "gapi.client.Error: Ma`projectId↵ at new mB (htt…ent (http://localhost:8887/scripts/app.js:3:1349)"}
initclient @ app.js:3
async function (async)
initclient @ app.js:3
(anonymous) @ app.js:3
(anonymous) @ api.js:15
fa @ api.js:8
A @ api.js:15
r.<computed> @ api.js:15
x.<computed> @ api.js:15
(anonymous) @ cb=gapi.loaded_1:1

Walter
 
Upvote 0

walterf25

Expert
Licensed User
Longtime User
Does Ma`projectId mean something in your language?
No it doesn't, i don't know where that is coming from tbh.

Just sent you a PM.

Walter
 
Upvote 0

walterf25

Expert
Licensed User
Longtime User
Does Ma`projectId mean something in your language?
I also get this error, if I don't comment out this line
B4X:
    Dim JsonObject As BANanoObject
    Dim prom As BANanoPromise = projects.RunMethod("lookup", Array(params))
    prom.then(JsonObject)   'if I leave this line uncommented I get an error

Uncaught SyntaxError: missing ) after argument list

Walter
 
Upvote 0

walterf25

Expert
Licensed User
Longtime User
In case anyone ever needs to work with Google Cloud DataStore, here's the code that worked for me, a BIG thanks to @alwaysbusy for all his help, this guy is the man.

B4X:
Sub LoadLib()
    Log("loaded client libs")
    gapi.Initialize("gapi")
    gapi.RunMethod("load", Array("client:auth2", BANano.CallBack(Me, "InitClient", Null)))
End Sub

Sub isSignedIn As Boolean
    Return gapi.GetField("auth2").RunMethod("getAuthInstance", Null).GetField("isSignedIn").RunMethod("get", Null)
End Sub

Sub InitClient()
    Dim params As Map
    params.Initialize
    params.Put("apiKey", API_KEY)
    params.Put("discoveryDocs", Array("https://datastore.googleapis.com/$discovery/rest?version=v1"))
    params.Put("clientId", Client_ID)
    params.Put("scope", "https://www.googleapis.com/auth/datastore")
   
    BANano.Await(gapi.GetField("client").RunMethod("init", params))
   '''SignIn
    Log("is SignedIn: " & isSignedIn)
    If isSignedIn Then
        gapi.GetField("client").RunMethod("setApiKey", API_KEY)
        Log("loaded url: " & gapi.GetField("client").RunMethod("load", "https://content.googleapis.com/discovery/v1/apis/datastore/v1beta3/rest"))
        LoadData
    Else
        SignIn
    End If
   
End Sub

Sub SignIn
    BANano.Await(gapi.GetField("auth2").RunMethod("getAuthInstance", Null).RunMethod("signIn", Null))
End Sub

This code is to Authenticate the user, It will check if the user has already been signed in, otherwise it will pop a window where the user can then sign in using their google email account, once signed in, the user will be allowed to continue.

Before doing any of this you need to load the appropriate libraries.
B4X:
    BANano.Header.AddJavascriptFile("https://apis.google.com/js/client.js")
    BANano.Header.AddJavascriptFile("https://apis.google.com/js/api.js")

I am also attaching here the CustomView I created for Plotly https://plotly.com/javascript/ a library I like to use to display charts, I like this library because it is very customizable, again thanks to @alwaysbusy for all his insight and big help on this as well.

After Signing (Authenticating) then you are ready to retrieve data from the DataStore database.
B4X:
Sub LoadData()
    Dim projects As BANanoObject = gapi.GetField("client").GetField("datastore").GetField("projects")
    Dim params As BANanoObject = $"
    {
      "projectId": "${ProjectID}",
      "resource": {
        "readOptions": {
          "readConsistency": "EVENTUAL"
        },
        "keys": [
          {
            "path": [
              {
                "kind": "${Kind}",
                "id": ${WaveFormID}
              }
            ],
            "partitionId": {
              "namespaceId": "current_drain_data",
              "projectId": "${ProjectID}"
            }
          }
        ]
      }
    }
    "$

    params = BANano.FromJson(params)
    Dim jsonStr As BANanoObject
    Dim err As Object
    Dim JsonObject As BANanoFetchResponse
    Dim prom As BANanoPromise = projects.RunMethod("lookup", params)
    prom.then(JsonObject)
    Return JsonObject.Body
    prom.Then(jsonStr)
    Log(BANano.IsJson(jsonStr))
    Log(BANano.IsString(jsonStr))
    Dim newObj As BANanoObject = BANano.FromJson(jsonStr) ' let(s make a javascript json object from it

    Dim founds() As BANanoObject = newObj.GetField("found") ' <----
    Log(founds)
    Dim current As List = founds(0).GetField("entity").GetField("properties").GetField("ch0").GetField("arrayValue").GetField("values")
    Dim time As List = founds(0).GetField("entity").GetField("properties").GetField("time").GetField("arrayValue").GetField("values")
   

    Dim currVals(current.Size) As Double
    Dim timeVals(time.Size) As Double
    For i = 0 To current.Size - 1
        Dim m1 As Map = current.Get(i)
        currVals(i) = m1.Get("doubleValue")
    Next
   
    Dim mm As Map = time.Get(0)
    Log("mm(0): " & mm.Get("doubleValue"))
    For j = 0 To time.Size - 1
        Dim m2 As Map = time.Get(j)
        timeVals(j) = m2.Get("doubleValue")
    Next
    Log("currVals size: " & currVals.Length)
    Log("timeVals size: " & timeVals.Length)
   
    Dim description As String = ""
    Dim err2 As BANanoObject
    description = founds(0).GetField("entity").GetField("properties").GetField("description").GetField("stringValue")
    Log("description: " & description)
    PlotlyChart1.InitializePlotly(description, timeVals, currVals)  'PlotlyChart1 is the variable created from the PlotlyChart customview class, the parameters passed are X axis values, y Axis values and description for Title.
    prom.Else(err)
    Log("error: " & err)
    BANano.ReturnThen("")
    prom.end
End Sub

WaveFormID, Kind variables are two fields that are generated inside of the DataStore database when you write data to it this are defined by the user, they can be named anything, in my case since I am using DataStore Because Excel Sheets does not have enough space to write thousands and thousands of data points.

Hope someone finds this useful!

Thanks,
Walter
 

Attachments

  • PlotlyChart.bas
    8 KB · Views: 152
Upvote 0
Top