I tried to build an HTTP server and opening it from a fyne app on android. If I run from the fyne app from the source folder it works but if I build it and try to run it on android the webapp breaks.
`
package main
import (
//"log"
"net/http"
//"strings"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/data/binding"
"fyne.io/fyne/v2/widget"
//"fyne.io/fyne/v2/canvas"
"net/url"
"fmt"
)
//var upgrader = websocket.Upgrader{}
var mainapp fyne.App
var MainTorrent string //magnet
var MainFile string //filepath
var AppIsClosing bool
func main() {
mainapp = app.New()
//mainapp.Settings().SetTheme(&myTheme{})
//mainapp.SetIcon(resourceAppiconPng)
mainwin := mainapp.NewWindow("wetorrent")
mainwin.Resize(fyne.NewSize(400, 710))
go startServer() //03
AppIsClosing = false
tabs := container.NewAppTabs(
container.NewTabItem("Home", homeScreen(mainwin)),
)
tabs.SetTabLocation(container.TabLocationTop)
mainwin.SetContent(tabs)
mainwin.ShowAndRun()
AppIsClosing = true
}
func homeScreen(win fyne.Window) fyne.CanvasObject {
data := binding.BindStringList(
//&[]string{"Item 1", "Item 2", "Item 3"},
&[]string{},
)
list := widget.NewListWithData(data,
func() fyne.CanvasObject {
return widget.NewLabel("template")
},
func(i binding.DataItem, o fyne.CanvasObject) {
o.(*widget.Label).Bind(i.(binding.String))
})
add := widget.NewButton("Open New Webapp Tab", func() {
//val := fmt.Sprintf("Item %d", data.Length()+1)
//data.Append(val)
openNewWebappTab()
fmt.Println("coool")
})
return container.NewBorder(add, nil, nil, nil, list)
}
func openNewWebappTab() {
u, err := url.Parse("http://localhost:8080/index.html")
if err != nil {
fmt.Println("error", err)
}
mainapp.OpenURL(u) //
}
func startServer() {
openNewWebappTab()
//
fs := http.FileServer(http.Dir("./Webapp"))
http.Handle("/", http.StripPrefix("/", fs))
fmt.Println(http.ListenAndServe(":8080", nil))
}
I was expecting to have an android app that will be able to run the webapp on android. How can I get the folder were to put the webapp on android and can the fyne compile command help me copy the web app?
Storage is different on each OS, a Fyne app knows it can use files at the location specified by App.Storage().RootURI().
I need to validate certificates generated by Android Key Attestation process on the server, however I don't have access to the real device yet.
How can I generate X.509 certificate with extension fields the same as I should get from the real device?
Obviously, the root certificate is going to be self-signed.
There are examples in https://github.com/google/android-key-attestation/tree/master/server/examples How to generate the same certificates with my values?
I prefer server side Java and Go.
There is no other way to generate real Google signed certificates other than using a real device.
For testing purposes, it's possible to generate Android certificates with the same values as if generated by Android Key Attestation process, however signed with other root certificate.
Expected certificate structure
Verifying hardware-backed key pairs with Key Attestation
Key and ID Attestation: Certificate extension data schema
Android Key Attestation Sample
Code below:
(1) Reads Google root certificate from file google-root-ca/google-1.pem and uses it as a template to create a new one with the same content.
(2) Creates Android certificate with Hardware Attested ID Serial signed by root certificate generated in step (1)
package main
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"encoding/pem"
"io/ioutil"
"math/big"
"time"
)
func main() {
rootCACertBytes, rootCAPrivKey, err := createCARoot()
if err != nil {
panic(err)
}
rootCACert, err := x509.ParseCertificate(rootCACertBytes)
if err != nil {
panic(err)
}
androidCertBytes, err := createAndroidKeystoreKey(rootCACert, rootCAPrivKey)
if err != nil {
panic(err)
}
err = printCertInPEM(rootCACertBytes, "CA.pem")
if err != nil {
panic(err)
}
err = printCertInPEM(androidCertBytes, "android.pem")
if err != nil {
panic(err)
}
}
func createCARoot() ([]byte, *rsa.PrivateKey, error) {
privKey, err := rsa.GenerateKey(rand.Reader, 4096)
if err != nil {
return nil, nil, err
}
certTemplate, err := readGoogleRootCA()
if err != nil {
return nil, nil, err
}
cert, err := x509.CreateCertificate(rand.Reader, certTemplate, certTemplate, privKey.Public(), privKey)
if err != nil {
return nil, nil, err
}
return cert, privKey, nil
}
func randomSerialNumber() (*big.Int, error) {
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
return rand.Int(rand.Reader, serialNumberLimit)
}
func readGoogleRootCA() (*x509.Certificate, error) {
pemBytes, err := ioutil.ReadFile("google-root-ca/google-1.pem")
if err != nil {
return nil, err
}
decoded, rest := pem.Decode(pemBytes)
_ = rest
return x509.ParseCertificate(decoded.Bytes)
}
func printCertInPEM(certBytes []byte, outFile string) error {
return ioutil.WriteFile(outFile, pem.EncodeToMemory(
&pem.Block{Type: "CERTIFICATE", Bytes: certBytes}), 0644)
}
var androidKeyAttestationOID = []int{1, 3, 6, 1, 4, 1, 11129, 2, 1, 17}
func createAndroidKeystoreKey(rootCACert *x509.Certificate, rootCAKey *rsa.PrivateKey) ([]byte, error) {
privKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return nil, err
}
expiration := time.Now().AddDate(10, 0, 0)
serialNumber, err := randomSerialNumber()
if err != nil {
return nil, err
}
androidKeyAttestationExtension, err := CreateAndroidKeyAttestationExtension()
if err != nil {
return nil, err
}
certTemplate := &x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
CommonName: "Android Keystore Key",
},
NotBefore: time.Now(),
NotAfter: expiration,
KeyUsage: x509.KeyUsageDigitalSignature,
ExtraExtensions: []pkix.Extension{
{
Id: androidKeyAttestationOID,
Value: androidKeyAttestationExtension,
},
},
}
return x509.CreateCertificate(rand.Reader, certTemplate, rootCACert, privKey.Public(), rootCAKey)
}
func CreateAndroidKeyAttestationExtension() ([]byte, error) {
keyDesc := KeyDescription{
AttestationVersion: 0,
AttestationSecurityLevel: 1,
KeymasterVersion: 0,
KeymasterSecurityLevel: 0,
AttestationChallenge: []byte("abcdefg"),
UniqueID: []byte{},
SoftwareEnforced: AuthorizationList{
AttestationIDSerial: []byte{},
},
TeeEnforced: AuthorizationList{
AttestationIDSerial: []byte("00112233445566778899"),
},
}
return asn1.Marshal(keyDesc)
}
type KeyDescription struct {
AttestationVersion int
AttestationSecurityLevel asn1.Enumerated
KeymasterVersion int
KeymasterSecurityLevel asn1.Enumerated
AttestationChallenge []byte
UniqueID []byte
SoftwareEnforced AuthorizationList
TeeEnforced AuthorizationList
}
// Values are commented out as nil values fail
type AuthorizationList struct {
//Purpose []int `asn1:"tag:1,explicit,set,optional"`
//Algorithm int `asn1:"tag:2,explicit,optional"`
//KeySize int `asn1:"tag:3,explicit,optional"`
//Digest []int `asn1:"tag:5,explicit,set,optional"`
//Padding []int `asn1:"tag:6,explicit,set,optional"`
//EcCurve int `asn1:"tag:10,explicit,optional"`
//RsaPublicExponent int `asn1:"tag:200,explicit,optional"`
//RollbackResistance interface{} `asn1:"tag:303,explicit,optional"`
//ActiveDateTime int `asn1:"tag:400,explicit,optional"`
//OriginationExpireDateTime int `asn1:"tag:401,explicit,optional"`
//UsageExpireDateTime int `asn1:"tag:402,explicit,optional"`
//NoAuthRequired interface{} `asn1:"tag:503,explicit,optional"`
//UserAuthType int `asn1:"tag:504,explicit,optional"`
//AuthTimeout int `asn1:"tag:505,explicit,optional"`
//AllowWhileOnBody interface{} `asn1:"tag:506,explicit,optional"`
//TrustedUserPresenceRequired interface{} `asn1:"tag:507,explicit,optional"`
//TrustedConfirmationRequired interface{} `asn1:"tag:508,explicit,optional"`
//UnlockedDeviceRequired interface{} `asn1:"tag:509,explicit,optional"`
//AllApplications interface{} `asn1:"tag:600,explicit,optional"`
//ApplicationID interface{} `asn1:"tag:601,explicit,optional"`
//CreationDateTime int `asn1:"tag:701,explicit,optional"`
//Origin int `asn1:"tag:702,explicit,optional"`
//RootOfTrust RootOfTrust `asn1:"tag:704,explicit,optional"`
//OsVersion int `asn1:"tag:705,explicit,optional"`
//OsPatchLevel int `asn1:"tag:706,explicit,optional"`
//AttestationApplicationID []byte `asn1:"tag:709,explicit,optional"`
//AttestationIDBrand []byte `asn1:"tag:710,explicit,optional"`
//AttestationIDDevice []byte `asn1:"tag:711,explicit,optional"`
//AttestationIDProduct []byte `asn1:"tag:712,explicit,optional"`
AttestationIDSerial []byte `asn1:"tag:713,explicit,optional"`
//AttestationIDImei []byte `asn1:"tag:714,explicit,optional"`
//AttestationIDMeid []byte `asn1:"tag:715,explicit,optional"`
//AttestationIDManufacturer []byte `asn1:"tag:716,explicit,optional"`
//AttestationIDModel []byte `asn1:"tag:717,explicit,optional"`
//VendorPatchLevel int `asn1:"tag:718,explicit,optional"`
//BootPatchLevel int `asn1:"tag:719,explicit,optional"`
}
type RootOfTrust struct {
VerifiedBootKey []byte
DeviceLocked bool
VerifiedBootState VerifiedBootState
VerifiedBootHash []byte
}
type VerifiedBootState int
const (
Verified VerifiedBootState = iota
SelfSigned
Unverified
Failed
)
google-root-ca/google-1.pem
Save the content of the second Google root certificate into google-root-ca/google-1.pem used in the code above.
I'm testing Rust with JNI async execution. I want to do execute requests in Rust and return the result to Android asynchronously with callback. I'm testing code to execute the request in the command line and it works fine.
That is how it works on command line:
Callback struck:
struct Processor {
pub(crate) callback: Box<dyn FnMut(String)>,
}
impl Processor {
fn set_callback(&mut self, c: impl FnMut(String) + 'static) {
self.callback = Box::new(c);
}
fn process_events(&mut self, result: String) {
(self.callback)(result);
}
}
Tokio/reqwest:
const DATA_URL: &str = "https://pokeapi.co/api/v2/pokemon/1/";
#[tokio::main]
pub async fn load_swapi_async_with_cb(callback: Box<dyn FnMut(String)>) -> Result<(), Box<dyn std::error::Error>> {
println!("load_swload_swapi_async_with_cbapi_async started");
let mut cb = Processor {
callback: Box::new(callback),
};
let body = reqwest::get(DATA_URL)
.await?
.json::<HashMap<String, String>>()
.await?;
//println!("{:#?}", body);
let name = match body.get("name") {
Some(name) => name,
None => "Failed to parse"
}.to_string();
println!("Name is: {} ", name);
cb.process_events(name);
Ok(())
}
And JNI part:
#[no_mangle]
#[allow(non_snake_case)]
pub extern "C" fn Java_com_omg_app_greetings_MainActivity_callback(env: JNIEnv,
_class: JClass,
callback: JObject) {
static callback: dyn FnMut(String) + 'static = |name| {
let response = env.new_string(&name).expect("Couldn't create java string!");
env.call_method(callback, "rustCallbackResult", "(Ljava/lang/String;)V",
&[JValue::from(JObject::from(response))]).unwrap();
};
pokemon_api(callback);
}
And pokemon API method:
#[no_mangle]
pub extern fn pokemon_api(callback: impl FnMut(String) + 'static) {
let cb_box = Box::new(callback);
swapi::load_swapi_async_with_cb(cb_box);
}
The error I'm facing:
JNI ENV env non-constant value:
let response = env.new_string(&name).expect("Couldn't create java string!");
| ^^^ non-constant value
callback - doesn't have a size known at compile-time:
static callback: dyn FnMut(String) + 'static = |name| {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time
I was checking how this working, but example seems to be out of date:
* https://github.com/mozilla/rust-android-gradle/blob/master/samples/rust/src/lib.rs
I fixed with my problem with the usage of the https://github.com/Dushistov/rust_swig
After you integrate it, it will autogenerate code and you can check how it does it.
I am studying on how to develop android sdk with gomobile, here is my use case:
The sdk will handle the file download and it will send its realtime received content to andoird, how could this be done?
I tried something like this, return a ReadCloser, then android will read from this stream :
func DownloadFile(url string) (io.ReadCloser, error) {
// download the file
resp, err := http.Get(url)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
err = errors.New(fmt.Sprintf("requesrting url [%s] error [%s]!", url, resp.Status))
return nil, err
}
return resp.Body, nil
}
however from the compiled java class, there isn't even this method, why?
And I tried to return a channel, but it is the same result, not event compiled in target java class.
Is there any better way to do that? does gomobile supports callback (go sdk call this callback registered by android)? I can hardly find any documentation on that callback usage.
After some googling, I found some useful information, here is a good presentation.
https://talks.madriguera.me/2016/gomobile.slide#17
Finnally, I made my code work:
// define call back interface
type Downloader interface {
Forward(content []byte)
}
// callback instance
var downloader Downloader
// save the callback instance
func RegisterCallback(c Downloader) {
downloader = c
}
func Download(url string) error {
// download the file
resp, err := http.Get(url)
if err != nil {
return err
}
if resp.StatusCode != http.StatusOK {
err = errors.New(fmt.Sprintf("requesrting url [%s] error [%s]!", url, resp.Status))
return err
}
defer resp.Body.Close()
buf := make([]byte, 256)
copyBuffer(resp.Body, buf)
return nil
}
func copyBuffer(src io.Reader, buf []byte) (err error) {
for {
nr, er := src.Read(buf)
if nr > 0 {
content := buf[0:nr]
// callback
downloader.Forward(content)
}
if er == io.EOF {
break
}
if er != nil {
err = er
break
}
}
return nil
}
In Android, I just implement forward interface, then it works well:
// remember call registerCallback() first
#Override
public void forward(byte[] bytes) {
System.out.println("--------------");
System.out.println(new String(bytes));
}
I am working on an IONIC Project,
while I am adding android platform, it gives me an error like this.
Error: Hook failed with error code 1: D:\IONIC Workspace\risecx-app\hooks\before_prepare\01_jshint.js
at C:\Users\HP\AppData\Roaming\npm\node_modules\cordova\node_modules\cordova-lib\src\hooks\HooksRunner.js:195:23
at _rejected (C:\Users\HP\AppData\Roaming\npm\node_modules\cordova\node_modules\q\q.js:797:24)
at C:\Users\HP\AppData\Roaming\npm\node_modules\cordova\node_modules\q\q.js:823:30
at Promise.when (C:\Users\HP\AppData\Roaming\npm\node_modules\cordova\node_modules\q\q.js:1035:31)
at Promise.promise.promiseDispatch (C:\Users\HP\AppData\Roaming\npm\node_modules\cordova\node_modules\q\q.js:741:41)
at C:\Users\HP\AppData\Roaming\npm\node_modules\cordova\node_modules\q\q.js:557:44
at flush (C:\Users\HP\AppData\Roaming\npm\node_modules\cordova\node_modules\q\q.js:108:17)
at doNTCallback0 (node.js:417:9)
at process._tickCallback (node.js:346:13)
my hooks\before_prepare\01_jshint.js file is like...
#!/usr/bin/env node
var fs = require('fs');
var path = require('path');
var jshint = require('jshint').JSHINT;
var async = require('async');
var foldersToProcess = [ 'js', 'js/services' ];
foldersToProcess.forEach(function(folder) {
processFiles("www/" + folder);
});
function processFiles(dir, callback) {
var errorCount = 0;
fs.readdir(dir, function(err, list) {
if (err) {
console.log('processFiles err: ' + err);
return;
}
async.eachSeries(list, function(file, innercallback) {
file = dir + '/' + file;
fs.stat(file, function(err, stat) {
if(!stat.isDirectory()) {
if(path.extname(file) === ".js") {
lintFile(file, function(hasError) {
if(hasError) {
errorCount++;
}
innercallback();
});
} else {
innercallback();
}
} else {
innercallback();
}
});
}, function(error) {
if(errorCount > 0) {
process.exit(1);
}
});
});
}
function lintFile(file, callback) {
console.log("Linting " + file);
fs.readFile(file, function(err, data) {
if(err) {
console.log('Error: ' + err);
return;
}
if(jshint(data.toString())) {
console.log('File ' + file + ' has no errors.');
console.log('-----------------------------------------');
callback(false);
} else {
console.log('Errors in file ' + file);
var out = jshint.data(),
errors = out.errors;
for(var j = 0; j < errors.length; j++) {
console.log(errors[j].line + ':' + errors[j].character + ' -> ' + errors[j].reason + ' -> ' +
errors[j].evidence);
}
console.log('-----------------------------------------');
callback(true);
}
});
}
I have worked with following commands....
npm install jshint --save
npm install q --save
npm install async
so any idea, where I am making a mistake....
Just delete your hooks directory and type this command.
ionic hooks add
It will solve your problem.
Please check your hooks directory have execute permissions.Give all permission to hooks and it's child folders
It also fails with error code 1 if you have a mistake in any of your files. Check to make sure it hasn't told you about any errors in you files before that.
In my case, I created an after_prepare hook to override cordova build process to use the actual versionCode specified in config.xml. I had the same error.
I solved it by adding "#!/usr/bin/env node" in my hook file(without quotes) at the top.That resolved everything. Hope it may help you .
I had a similar issue with a Moodle Mobile 2 build returning this error - I was missing the 'node_modules' folder from the main directory for the build to complete.
Hope this helps