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.
As title,
I try to call
cmd := exec.Command(commit_id, "=", "$(git rev-parse --short HEAD)")
cmd.Run()
fmt.Println("commit_id = ", commit_id);
But the result is null.
Does somebody know how to parse git commit-id?
Thanks!
Here is a sample.
package main
import (
"bytes"
"fmt"
"os/exec"
)
func main() {
var commitid string
var sout, serr bytes.Buffer
cmd := exec.Command("git", "rev-parse", "--short", "HEAD")
cmd.Stdout = &sout
cmd.Stderr = &serr
err := cmd.Run()
if err != nil {
fmt.Println(serr.String())
} else {
commitid = sout.String()
fmt.Println("commit_id =", commitid)
}
}
I am new to Golang. The code could be better. I followed the example in Command and it seems that it works this way.
Save the below code as commit_hash.go
import (
"bytes"
"fmt"
"os"
"os/exec"
)
func main() {
commit, err := commitHash()
if err != nil {
fmt.Printf("\x1b[31;1m%s\x1b[0m\n", fmt.Sprintf("error: %s", err))
os.Exit(1)
}
fmt.Printf("commit_id=%s", commit)
}
func commitHash() (string, error) {
cmd := exec.Command("git", "rev-parse", "--short", "HEAD")
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
return "", fmt.Errorf("%w; %s", err, stderr.String())
}
return stdout.String(), nil
}
then run it from your git repository:
go run commit_hash.go
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.
From what I can tell most of the flutter guides out there can open from local storage, but nothing about file sharing. Anybody know how to do this. This is a guide in enabling it specifically for ios https://developer.apple.com/library/archive/qa/qa1587/_index.html.
I mean there is the https://pub.dartlang.org/packages/open_file extension, but opens from the file storage.
To clarify this question isn't about sharing a file from the app with another, but when sharing from an external app being prompted to open in this flutter app.
To do this in iOS you first define the Document Types and Imported UTIs in XCode as described in the guide you mentioned, and then in your AppDelegate.m file you do:
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
/* custom code begin */
FlutterViewController* controller = (FlutterViewController*)self.window.rootViewController;
FlutterMethodChannel* myChannel = [FlutterMethodChannel
methodChannelWithName:#"my/file"
binaryMessenger:controller];
__block NSURL *initialURL = launchOptions[UIApplicationLaunchOptionsURLKey];
[myChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
if ([#"checkintent" isEqualToString:call.method]) {
if (initialURL) {
[myChannel invokeMethod:#"loaded" arguments: [initialURL absoluteString]];
initialURL = nil;
result(#TRUE);
}
}
}];
/* custom code end */
[GeneratedPluginRegistrant registerWithRegistry:self];
// Override point for customization after application launch.
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
On the Dart side:
class PlayTextPageState extends State<MyHomePage> with WidgetsBindingObserver{
static const platform = const MethodChannel('my/file');
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
platform.setMethodCallHandler((MethodCall call) async {
String method = call.method;
if (method == 'loaded') {
String path = call.arguments; // this is the path
...
}
});
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
if (state == AppLifecycleState.paused) {
...
} else if (state == AppLifecycleState.resumed) {
platform.invokeMethod("checkintent")
.then((result) {
// result == 1 if the app was opened with a file
});
}
}
}
Adding on to lastant's answer, you actually also need to override application(_:open:options:) in AppDelegate.swift for this to work.
So the idea is to use UIActivityViewController in iOS to open a file in Flutter (eg: restore a backup of the SQL DB into the Flutter app from an email).
First, you need to set the UTIs in the info.plist. Here's a good link to explain how that works. https://www.raywenderlich.com/813044-uiactivityviewcontroller-tutorial-sharing-data
Second, add the channel controller code in AppDelegate.swift.
We also need to override application(:open:options:) in AppDelegate.swift because iOS will invoke application(:open:options:) when an external application wants to send your application a file. Hence we store the filename as a variable inside AppDelegate.
Here we are have a 2-way channel controller between iOS and Flutter. Everytime the Flutter app enter the AppLifecycleState.resumed state, it will invoke "checkIntent" to check back into AppDelegate to see if the filename has been set. If a filename has been set, AppDelegate will invoke the "load" method in flutter whereby you do your required processing with the file.
Remember to delete the file given to you from AppDelegate after you are done with your processing. Otherwise, it will bloat up your application.
#UIApplicationMain
#objc class AppDelegate: FlutterAppDelegate {
var initialURL: URL?
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
/* channel controller code */
let controller: FlutterViewController = self.window?.rootViewController as! FlutterViewController
let myChannel = FlutterMethodChannel(name: "my/file", binaryMessenger: controller.binaryMessenger)
myChannel.setMethodCallHandler({(call: FlutterMethodCall, result: #escaping FlutterResult)-> Void in
if(call.method == "checkintent"){
if(self.initialURL != nil){
myChannel.invokeMethod("loaded", arguments: self.initialURL?.absoluteString );
self.initialURL = nil;
result(true);
} else{
print("initialURL is null");
}
} else{
print("no such channel method");
}
});
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
override func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
print("import URL: \(url)");
initialURL = url;
// should not remove here.. remove after i get into flutter...
// try? FileManager.default.removeItem(at: url);
return true;
}
}