From 08623f0bae9c41a3dab7ba0f2ff798d0b4c44ff0 Mon Sep 17 00:00:00 2001 From: Jono Wenger <jono@elixxir.io> Date: Tue, 6 Sep 2022 14:25:41 -0700 Subject: [PATCH] Write client setup and sending e2e message in javascript --- examples/ndf.json | 1 + examples/sendE2E/index.html | 67 ++++ examples/sendE2E/index2.html | 59 ++++ examples/sendE2E/xxdk.js | 225 ++++++++++++ examples/styles.css | 82 +++++ examples/wasm_exec.js | 650 +++++++++++++++++++++++++++++++++++ main.go | 11 + wasm/cmix.go | 24 +- wasm/e2eHandler.go | 19 +- 9 files changed, 1125 insertions(+), 13 deletions(-) create mode 100644 examples/ndf.json create mode 100644 examples/sendE2E/index.html create mode 100644 examples/sendE2E/index2.html create mode 100644 examples/sendE2E/xxdk.js create mode 100644 examples/styles.css create mode 100644 examples/wasm_exec.js diff --git a/examples/ndf.json b/examples/ndf.json new file mode 100644 index 00000000..0ca92937 --- /dev/null +++ b/examples/ndf.json @@ -0,0 +1 @@ +{"Timestamp":"2022-09-02T10:27:19.6661079-07:00","Gateways":[{"Id":"uKOO90b6GDwXcLr/hGU3sjX6O0AmDA4nvC4bDEO7wFMB","Address":"0.0.0.0:8440","Tls_certificate":"-----BEGIN CERTIFICATE-----\r\nMIIDbDCCAlSgAwIBAgIJAOUNtZneIYECMA0GCSqGSIb3DQEBBQUAMGgxCzAJBgNV\r\nBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQHDAlDbGFyZW1vbnQx\r\nGzAZBgNVBAoMElByaXZhdGVncml0eSBDb3JwLjETMBEGA1UEAwwKKi5jbWl4LnJp\r\ncDAeFw0xOTAzMDUxODM1NDNaFw0yOTAzMDIxODM1NDNaMGgxCzAJBgNVBAYTAlVT\r\nMRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQHDAlDbGFyZW1vbnQxGzAZBgNV\r\nBAoMElByaXZhdGVncml0eSBDb3JwLjETMBEGA1UEAwwKKi5jbWl4LnJpcDCCASIw\r\nDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPP0WyVkfZA/CEd2DgKpcudn0oDh\r\nDwsjmx8LBDWsUgQzyLrFiVigfUmUefknUH3dTJjmiJtGqLsayCnWdqWLHPJYvFfs\r\nWYW0IGF93UG/4N5UAWO4okC3CYgKSi4ekpfw2zgZq0gmbzTnXcHF9gfmQ7jJUKSE\r\ntJPSNzXq+PZeJTC9zJAb4Lj8QzH18rDM8DaL2y1ns0Y2Hu0edBFn/OqavBJKb/uA\r\nm3AEjqeOhC7EQUjVamWlTBPt40+B/6aFJX5BYm2JFkRsGBIyBVL46MvC02MgzTT9\r\nbJIJfwqmBaTruwemNgzGu7Jk03hqqS1TUEvSI6/x8bVoba3orcKkf9HsDjECAwEA\r\nAaMZMBcwFQYDVR0RBA4wDIIKKi5jbWl4LnJpcDANBgkqhkiG9w0BAQUFAAOCAQEA\r\nneUocN4AbcQAC1+b3To8u5UGdaGxhcGyZBlAoenRVdjXK3lTjsMdMWb4QctgNfIf\r\nU/zuUn2mxTmF/ekP0gCCgtleZr9+DYKU5hlXk8K10uKxGD6EvoiXZzlfeUuotgp2\r\nqvI3ysOm/hvCfyEkqhfHtbxjV7j7v7eQFPbvNaXbLa0yr4C4vMK/Z09Ui9JrZ/Z4\r\ncyIkxfC6/rOqAirSdIp09EGiw7GM8guHyggE4IiZrDslT8V3xIl985cbCxSxeW1R\r\ntgH4rdEXuVe9+31oJhmXOE9ux2jCop9tEJMgWg7HStrJ5plPbb+HmjoX3nBO04E5\r\n6m52PyzMNV+2N21IPppKwA==\r\n-----END CERTIFICATE-----\r\n","Bin":"SouthAndCentralAmerica"},{"Id":"KDzk21/pQ01RtoOHdh7207dkUqx5Mq7MIJsRPsOKXcMB","Address":"0.0.0.0:8442","Tls_certificate":"-----BEGIN CERTIFICATE-----\r\nMIIDbDCCAlSgAwIBAgIJAOUNtZneIYECMA0GCSqGSIb3DQEBBQUAMGgxCzAJBgNV\r\nBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQHDAlDbGFyZW1vbnQx\r\nGzAZBgNVBAoMElByaXZhdGVncml0eSBDb3JwLjETMBEGA1UEAwwKKi5jbWl4LnJp\r\ncDAeFw0xOTAzMDUxODM1NDNaFw0yOTAzMDIxODM1NDNaMGgxCzAJBgNVBAYTAlVT\r\nMRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQHDAlDbGFyZW1vbnQxGzAZBgNV\r\nBAoMElByaXZhdGVncml0eSBDb3JwLjETMBEGA1UEAwwKKi5jbWl4LnJpcDCCASIw\r\nDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPP0WyVkfZA/CEd2DgKpcudn0oDh\r\nDwsjmx8LBDWsUgQzyLrFiVigfUmUefknUH3dTJjmiJtGqLsayCnWdqWLHPJYvFfs\r\nWYW0IGF93UG/4N5UAWO4okC3CYgKSi4ekpfw2zgZq0gmbzTnXcHF9gfmQ7jJUKSE\r\ntJPSNzXq+PZeJTC9zJAb4Lj8QzH18rDM8DaL2y1ns0Y2Hu0edBFn/OqavBJKb/uA\r\nm3AEjqeOhC7EQUjVamWlTBPt40+B/6aFJX5BYm2JFkRsGBIyBVL46MvC02MgzTT9\r\nbJIJfwqmBaTruwemNgzGu7Jk03hqqS1TUEvSI6/x8bVoba3orcKkf9HsDjECAwEA\r\nAaMZMBcwFQYDVR0RBA4wDIIKKi5jbWl4LnJpcDANBgkqhkiG9w0BAQUFAAOCAQEA\r\nneUocN4AbcQAC1+b3To8u5UGdaGxhcGyZBlAoenRVdjXK3lTjsMdMWb4QctgNfIf\r\nU/zuUn2mxTmF/ekP0gCCgtleZr9+DYKU5hlXk8K10uKxGD6EvoiXZzlfeUuotgp2\r\nqvI3ysOm/hvCfyEkqhfHtbxjV7j7v7eQFPbvNaXbLa0yr4C4vMK/Z09Ui9JrZ/Z4\r\ncyIkxfC6/rOqAirSdIp09EGiw7GM8guHyggE4IiZrDslT8V3xIl985cbCxSxeW1R\r\ntgH4rdEXuVe9+31oJhmXOE9ux2jCop9tEJMgWg7HStrJ5plPbb+HmjoX3nBO04E5\r\n6m52PyzMNV+2N21IPppKwA==\r\n-----END CERTIFICATE-----\r\n","Bin":"CentralEurope"},{"Id":"jgZt6uZkK6tTGRnk0tAmVO9Akv11y0yBPolNkw/e+bAB","Address":"0.0.0.0:8443","Tls_certificate":"-----BEGIN CERTIFICATE-----\r\nMIIDbDCCAlSgAwIBAgIJAOUNtZneIYECMA0GCSqGSIb3DQEBBQUAMGgxCzAJBgNV\r\nBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQHDAlDbGFyZW1vbnQx\r\nGzAZBgNVBAoMElByaXZhdGVncml0eSBDb3JwLjETMBEGA1UEAwwKKi5jbWl4LnJp\r\ncDAeFw0xOTAzMDUxODM1NDNaFw0yOTAzMDIxODM1NDNaMGgxCzAJBgNVBAYTAlVT\r\nMRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQHDAlDbGFyZW1vbnQxGzAZBgNV\r\nBAoMElByaXZhdGVncml0eSBDb3JwLjETMBEGA1UEAwwKKi5jbWl4LnJpcDCCASIw\r\nDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPP0WyVkfZA/CEd2DgKpcudn0oDh\r\nDwsjmx8LBDWsUgQzyLrFiVigfUmUefknUH3dTJjmiJtGqLsayCnWdqWLHPJYvFfs\r\nWYW0IGF93UG/4N5UAWO4okC3CYgKSi4ekpfw2zgZq0gmbzTnXcHF9gfmQ7jJUKSE\r\ntJPSNzXq+PZeJTC9zJAb4Lj8QzH18rDM8DaL2y1ns0Y2Hu0edBFn/OqavBJKb/uA\r\nm3AEjqeOhC7EQUjVamWlTBPt40+B/6aFJX5BYm2JFkRsGBIyBVL46MvC02MgzTT9\r\nbJIJfwqmBaTruwemNgzGu7Jk03hqqS1TUEvSI6/x8bVoba3orcKkf9HsDjECAwEA\r\nAaMZMBcwFQYDVR0RBA4wDIIKKi5jbWl4LnJpcDANBgkqhkiG9w0BAQUFAAOCAQEA\r\nneUocN4AbcQAC1+b3To8u5UGdaGxhcGyZBlAoenRVdjXK3lTjsMdMWb4QctgNfIf\r\nU/zuUn2mxTmF/ekP0gCCgtleZr9+DYKU5hlXk8K10uKxGD6EvoiXZzlfeUuotgp2\r\nqvI3ysOm/hvCfyEkqhfHtbxjV7j7v7eQFPbvNaXbLa0yr4C4vMK/Z09Ui9JrZ/Z4\r\ncyIkxfC6/rOqAirSdIp09EGiw7GM8guHyggE4IiZrDslT8V3xIl985cbCxSxeW1R\r\ntgH4rdEXuVe9+31oJhmXOE9ux2jCop9tEJMgWg7HStrJ5plPbb+HmjoX3nBO04E5\r\n6m52PyzMNV+2N21IPppKwA==\r\n-----END CERTIFICATE-----\r\n","Bin":"EasternEurope"},{"Id":"AFARX39tIj246msuyhutEWlFQv7LSaAe8cR0i+aB59gB","Address":"0.0.0.0:8444","Tls_certificate":"-----BEGIN CERTIFICATE-----\r\nMIIDbDCCAlSgAwIBAgIJAOUNtZneIYECMA0GCSqGSIb3DQEBBQUAMGgxCzAJBgNV\r\nBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQHDAlDbGFyZW1vbnQx\r\nGzAZBgNVBAoMElByaXZhdGVncml0eSBDb3JwLjETMBEGA1UEAwwKKi5jbWl4LnJp\r\ncDAeFw0xOTAzMDUxODM1NDNaFw0yOTAzMDIxODM1NDNaMGgxCzAJBgNVBAYTAlVT\r\nMRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQHDAlDbGFyZW1vbnQxGzAZBgNV\r\nBAoMElByaXZhdGVncml0eSBDb3JwLjETMBEGA1UEAwwKKi5jbWl4LnJpcDCCASIw\r\nDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPP0WyVkfZA/CEd2DgKpcudn0oDh\r\nDwsjmx8LBDWsUgQzyLrFiVigfUmUefknUH3dTJjmiJtGqLsayCnWdqWLHPJYvFfs\r\nWYW0IGF93UG/4N5UAWO4okC3CYgKSi4ekpfw2zgZq0gmbzTnXcHF9gfmQ7jJUKSE\r\ntJPSNzXq+PZeJTC9zJAb4Lj8QzH18rDM8DaL2y1ns0Y2Hu0edBFn/OqavBJKb/uA\r\nm3AEjqeOhC7EQUjVamWlTBPt40+B/6aFJX5BYm2JFkRsGBIyBVL46MvC02MgzTT9\r\nbJIJfwqmBaTruwemNgzGu7Jk03hqqS1TUEvSI6/x8bVoba3orcKkf9HsDjECAwEA\r\nAaMZMBcwFQYDVR0RBA4wDIIKKi5jbWl4LnJpcDANBgkqhkiG9w0BAQUFAAOCAQEA\r\nneUocN4AbcQAC1+b3To8u5UGdaGxhcGyZBlAoenRVdjXK3lTjsMdMWb4QctgNfIf\r\nU/zuUn2mxTmF/ekP0gCCgtleZr9+DYKU5hlXk8K10uKxGD6EvoiXZzlfeUuotgp2\r\nqvI3ysOm/hvCfyEkqhfHtbxjV7j7v7eQFPbvNaXbLa0yr4C4vMK/Z09Ui9JrZ/Z4\r\ncyIkxfC6/rOqAirSdIp09EGiw7GM8guHyggE4IiZrDslT8V3xIl985cbCxSxeW1R\r\ntgH4rdEXuVe9+31oJhmXOE9ux2jCop9tEJMgWg7HStrJ5plPbb+HmjoX3nBO04E5\r\n6m52PyzMNV+2N21IPppKwA==\r\n-----END CERTIFICATE-----\r\n","Bin":"MiddleEast"},{"Id":"EyVHEz37phB8XZVl2N6Lu3p/bxnVIBvoAPhbPXIcpZsB","Address":"0.0.0.0:8441","Tls_certificate":"-----BEGIN CERTIFICATE-----\r\nMIIDbDCCAlSgAwIBAgIJAOUNtZneIYECMA0GCSqGSIb3DQEBBQUAMGgxCzAJBgNV\r\nBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQHDAlDbGFyZW1vbnQx\r\nGzAZBgNVBAoMElByaXZhdGVncml0eSBDb3JwLjETMBEGA1UEAwwKKi5jbWl4LnJp\r\ncDAeFw0xOTAzMDUxODM1NDNaFw0yOTAzMDIxODM1NDNaMGgxCzAJBgNVBAYTAlVT\r\nMRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQHDAlDbGFyZW1vbnQxGzAZBgNV\r\nBAoMElByaXZhdGVncml0eSBDb3JwLjETMBEGA1UEAwwKKi5jbWl4LnJpcDCCASIw\r\nDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPP0WyVkfZA/CEd2DgKpcudn0oDh\r\nDwsjmx8LBDWsUgQzyLrFiVigfUmUefknUH3dTJjmiJtGqLsayCnWdqWLHPJYvFfs\r\nWYW0IGF93UG/4N5UAWO4okC3CYgKSi4ekpfw2zgZq0gmbzTnXcHF9gfmQ7jJUKSE\r\ntJPSNzXq+PZeJTC9zJAb4Lj8QzH18rDM8DaL2y1ns0Y2Hu0edBFn/OqavBJKb/uA\r\nm3AEjqeOhC7EQUjVamWlTBPt40+B/6aFJX5BYm2JFkRsGBIyBVL46MvC02MgzTT9\r\nbJIJfwqmBaTruwemNgzGu7Jk03hqqS1TUEvSI6/x8bVoba3orcKkf9HsDjECAwEA\r\nAaMZMBcwFQYDVR0RBA4wDIIKKi5jbWl4LnJpcDANBgkqhkiG9w0BAQUFAAOCAQEA\r\nneUocN4AbcQAC1+b3To8u5UGdaGxhcGyZBlAoenRVdjXK3lTjsMdMWb4QctgNfIf\r\nU/zuUn2mxTmF/ekP0gCCgtleZr9+DYKU5hlXk8K10uKxGD6EvoiXZzlfeUuotgp2\r\nqvI3ysOm/hvCfyEkqhfHtbxjV7j7v7eQFPbvNaXbLa0yr4C4vMK/Z09Ui9JrZ/Z4\r\ncyIkxfC6/rOqAirSdIp09EGiw7GM8guHyggE4IiZrDslT8V3xIl985cbCxSxeW1R\r\ntgH4rdEXuVe9+31oJhmXOE9ux2jCop9tEJMgWg7HStrJ5plPbb+HmjoX3nBO04E5\r\n6m52PyzMNV+2N21IPppKwA==\r\n-----END CERTIFICATE-----\r\n","Bin":"WesternEurope"}],"Nodes":[{"Id":"uKOO90b6GDwXcLr/hGU3sjX6O0AmDA4nvC4bDEO7wFMC","Address":"","Tls_certificate":"","Status":0},{"Id":"KDzk21/pQ01RtoOHdh7207dkUqx5Mq7MIJsRPsOKXcMC","Address":"","Tls_certificate":"","Status":0},{"Id":"jgZt6uZkK6tTGRnk0tAmVO9Akv11y0yBPolNkw/e+bAC","Address":"","Tls_certificate":"","Status":0},{"Id":"AFARX39tIj246msuyhutEWlFQv7LSaAe8cR0i+aB59gC","Address":"","Tls_certificate":"","Status":0},{"Id":"EyVHEz37phB8XZVl2N6Lu3p/bxnVIBvoAPhbPXIcpZsC","Address":"","Tls_certificate":"","Status":0}],"Registration":{"Address":":18000","ClientRegistrationAddress":"0.0.0.0:11421","Tls_certificate":"-----BEGIN CERTIFICATE-----\r\nMIIDbDCCAlSgAwIBAgIJAOUNtZneIYECMA0GCSqGSIb3DQEBBQUAMGgxCzAJBgNV\r\nBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQHDAlDbGFyZW1vbnQx\r\nGzAZBgNVBAoMElByaXZhdGVncml0eSBDb3JwLjETMBEGA1UEAwwKKi5jbWl4LnJp\r\ncDAeFw0xOTAzMDUxODM1NDNaFw0yOTAzMDIxODM1NDNaMGgxCzAJBgNVBAYTAlVT\r\nMRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQHDAlDbGFyZW1vbnQxGzAZBgNV\r\nBAoMElByaXZhdGVncml0eSBDb3JwLjETMBEGA1UEAwwKKi5jbWl4LnJpcDCCASIw\r\nDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPP0WyVkfZA/CEd2DgKpcudn0oDh\r\nDwsjmx8LBDWsUgQzyLrFiVigfUmUefknUH3dTJjmiJtGqLsayCnWdqWLHPJYvFfs\r\nWYW0IGF93UG/4N5UAWO4okC3CYgKSi4ekpfw2zgZq0gmbzTnXcHF9gfmQ7jJUKSE\r\ntJPSNzXq+PZeJTC9zJAb4Lj8QzH18rDM8DaL2y1ns0Y2Hu0edBFn/OqavBJKb/uA\r\nm3AEjqeOhC7EQUjVamWlTBPt40+B/6aFJX5BYm2JFkRsGBIyBVL46MvC02MgzTT9\r\nbJIJfwqmBaTruwemNgzGu7Jk03hqqS1TUEvSI6/x8bVoba3orcKkf9HsDjECAwEA\r\nAaMZMBcwFQYDVR0RBA4wDIIKKi5jbWl4LnJpcDANBgkqhkiG9w0BAQUFAAOCAQEA\r\nneUocN4AbcQAC1+b3To8u5UGdaGxhcGyZBlAoenRVdjXK3lTjsMdMWb4QctgNfIf\r\nU/zuUn2mxTmF/ekP0gCCgtleZr9+DYKU5hlXk8K10uKxGD6EvoiXZzlfeUuotgp2\r\nqvI3ysOm/hvCfyEkqhfHtbxjV7j7v7eQFPbvNaXbLa0yr4C4vMK/Z09Ui9JrZ/Z4\r\ncyIkxfC6/rOqAirSdIp09EGiw7GM8guHyggE4IiZrDslT8V3xIl985cbCxSxeW1R\r\ntgH4rdEXuVe9+31oJhmXOE9ux2jCop9tEJMgWg7HStrJ5plPbb+HmjoX3nBO04E5\r\n6m52PyzMNV+2N21IPppKwA==\r\n-----END CERTIFICATE-----\r\n","EllipticPubKey":"AWjTOI++PCwzIVLMiXcBHzKtxWShdbBLluGOtbnqd10="},"Notification":{"Address":"","Tls_certificate":""},"Udb":{"Id":"uu49Kr9myQUG0gG8VKwalxmem65pfPu4D2Lk1u8pi/UD","Cert":"-----BEGIN CERTIFICATE-----\r\nMIIDbDCCAlSgAwIBAgIJAOUNtZneIYECMA0GCSqGSIb3DQEBBQUAMGgxCzAJBgNV\r\nBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQHDAlDbGFyZW1vbnQx\r\nGzAZBgNVBAoMElByaXZhdGVncml0eSBDb3JwLjETMBEGA1UEAwwKKi5jbWl4LnJp\r\ncDAeFw0xOTAzMDUxODM1NDNaFw0yOTAzMDIxODM1NDNaMGgxCzAJBgNVBAYTAlVT\r\nMRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQHDAlDbGFyZW1vbnQxGzAZBgNV\r\nBAoMElByaXZhdGVncml0eSBDb3JwLjETMBEGA1UEAwwKKi5jbWl4LnJpcDCCASIw\r\nDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPP0WyVkfZA/CEd2DgKpcudn0oDh\r\nDwsjmx8LBDWsUgQzyLrFiVigfUmUefknUH3dTJjmiJtGqLsayCnWdqWLHPJYvFfs\r\nWYW0IGF93UG/4N5UAWO4okC3CYgKSi4ekpfw2zgZq0gmbzTnXcHF9gfmQ7jJUKSE\r\ntJPSNzXq+PZeJTC9zJAb4Lj8QzH18rDM8DaL2y1ns0Y2Hu0edBFn/OqavBJKb/uA\r\nm3AEjqeOhC7EQUjVamWlTBPt40+B/6aFJX5BYm2JFkRsGBIyBVL46MvC02MgzTT9\r\nbJIJfwqmBaTruwemNgzGu7Jk03hqqS1TUEvSI6/x8bVoba3orcKkf9HsDjECAwEA\r\nAaMZMBcwFQYDVR0RBA4wDIIKKi5jbWl4LnJpcDANBgkqhkiG9w0BAQUFAAOCAQEA\r\nneUocN4AbcQAC1+b3To8u5UGdaGxhcGyZBlAoenRVdjXK3lTjsMdMWb4QctgNfIf\r\nU/zuUn2mxTmF/ekP0gCCgtleZr9+DYKU5hlXk8K10uKxGD6EvoiXZzlfeUuotgp2\r\nqvI3ysOm/hvCfyEkqhfHtbxjV7j7v7eQFPbvNaXbLa0yr4C4vMK/Z09Ui9JrZ/Z4\r\ncyIkxfC6/rOqAirSdIp09EGiw7GM8guHyggE4IiZrDslT8V3xIl985cbCxSxeW1R\r\ntgH4rdEXuVe9+31oJhmXOE9ux2jCop9tEJMgWg7HStrJ5plPbb+HmjoX3nBO04E5\r\n6m52PyzMNV+2N21IPppKwA==\r\n-----END CERTIFICATE-----\r\n","Address":"127.0.0.1:18001","DhPubKey":"eyJWYWx1ZSI6MTQ3MTM5NzkxNDA3Njk5MjM0OTk3MTkyOTEwNTk1MDc1NTI3NDM1NDYwMDYxNTI0NzcyOTQ0MzQzNzQ4NjgxMjA2MjY1ODY5ODQ2NjQyMzMyMzQ1NzA3OTkwNjc3NDA2MDQwMTQ1MTc3NTM0MzIxNTM4MTgyNDQwNzE3ODkwMzU5OTkzNDI4Njg2MTA5NTI1MjAyMTE4MjA1MjIzODkxNDMxODk5MTY4MjYxOTU5MTIzOTc3MDcxMzYzMDIzMTYxOTgzNTIwNjIwNDA1MjM1NzQ3NTkwMDg0NjkzNjYxODA0MTE1NjkxNjc3NDg4MDQ5MzY3NTIxMTk1Nzk1NjAyOTcxNjI1MzYzODY0NDkyOTk5NDk2NTI3MTkwNjA2MzgzNTkzMjg5NjA3MDk3MTg1MjEwNzkyOTk5OTgyNjY3Mjc5MDk1Njc4ODI2NTE1OTA5NTA2MDQyNTA4MDgxNDU2NDkzNDU2OTk3MzU0MDQwNTA0NDM2NTMwMTI2OTQwNDM0MzkxNjEyMTMyNTcyMjEwNDY5OTExMDY4NjAyMzQ2MTExNjAyOTUwNzQyNzk2NzUyNDQ2MDQ0MTk3OTM4OTQ2ODg1ODQ1MTE5NzMzNjU0NzQzOTc1OTY4NTIwNzYyNDgyODI3MzY1NDM0MDMxNDc5NjEzOTE3MTQ5ODgzNjQwMzQ3ODU1NTEyODY0ODc5NTU2NjY5ODcxNjI1NzAyMzczNTU1MTkxMTE5NjM2NDE0MDg4NDc2NjUyODc4NTYzNzAwNzE2NTAxNzk5NDA5NjA3NDI3Njg5OTI5MTcxMjY2MTQ3MDQ3NzU2MjA0Njg5NDM5NTE5MDkyNjgwNDk0ODg2MTMxNDc0NjY1MzMxNzE4MDY0MjQ3NzQ0MTE5MTQ2Mjg4MDY0NjIwOTMzMjQ4OTg3MjAzNzc4MjQxNTEzNTQ2MTc1NDQ1NjM4ODU4MjU0MTk0NTQ4MjY3NjIwOTYwNTEwMjc0Nzc2NjU4Mzc3MDM4OTcxNzkzMDUxOTAxNTk1NDkyNDQ1OTQzNjk3NTE5NTExMjAzOTkzMzgxOTAxNjI3NzY2Mjk1MDc1NDQ0NTM3NDIyMTc5ODgxMDI3NjcwMTE4MzM2NzgzODA5MjcxODAxNTMwNjk4MzE5ODIxNjg2Mzc1ODc5NTY1NzgwMDcxMjE0MzMwOTA5ODk5MTU1MTExNjQ3Mjc5NTg5NDc0ODIyMzkzMDQ3OTU1NzMyMTY3NzI4ODI1NTMyNDQ1ODg4MDk2MTY1NzIxMzExNDcxNTIyODUxLCJGaW5nZXJwcmludCI6MTY4MDE1NDE1MTEyMzMwOTgzNjN9"},"E2e":{"Prime":"E2EE983D031DC1DB6F1A7A67DF0E9A8E5561DB8E8D49413394C049B7A8ACCEDC298708F121951D9CF920EC5D146727AA4AE535B0922C688B55B3DD2AEDF6C01C94764DAB937935AA83BE36E67760713AB44A6337C20E7861575E745D31F8B9E9AD8412118C62A3E2E29DF46B0864D0C951C394A5CBBDC6ADC718DD2A3E041023DBB5AB23EBB4742DE9C1687B5B34FA48C3521632C4A530E8FFB1BC51DADDF453B0B2717C2BC6669ED76B4BDD5C9FF558E88F26E5785302BEDBCA23EAC5ACE92096EE8A60642FB61E8F3D24990B8CB12EE448EEF78E184C7242DD161C7738F32BF29A841698978825B4111B4BC3E1E198455095958333D776D8B2BEEED3A1A1A221A6E37E664A64B83981C46FFDDC1A45E3D5211AAF8BFBC072768C4F50D7D7803D2D4F278DE8014A47323631D7E064DE81C0C6BFA43EF0E6998860F1390B5D3FEACAF1696015CB79C3F9C2D93D961120CD0E5F12CBB687EAB045241F96789C38E89D796138E6319BE62E35D87B1048CA28BE389B575E994DCA755471584A09EC723742DC35873847AEF49F66E43873","Small_prime":"","Generator":"2"},"Cmix":{"Prime":"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199FFFFFFFFFFFFFFFF","Small_prime":"","Generator":"2"},"AddressSpace":[{"Size":32,"Timestamp":"2022-09-02T10:27:19.6392244-07:00"}],"ClientVersion":"","WhitelistedIds":null,"WhitelistedIpAddresses":null,"RateLimits":{"Capacity":0,"LeakedTokens":0,"LeakDuration":0}} \ No newline at end of file diff --git a/examples/sendE2E/index.html b/examples/sendE2E/index.html new file mode 100644 index 00000000..90c544f3 --- /dev/null +++ b/examples/sendE2E/index.html @@ -0,0 +1,67 @@ +<!-- + ~ Copyright © 2020 xx network SEZC /// + ~ /// + ~ Use of this source code is governed by a license that can be found in the /// + ~ LICENSE file /// + --> + +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <title>xxDk WebAssembly Test</title> + + <link rel="stylesheet" type="text/css" href="../styles.css"> + <script type="text/javascript" src="xxdk.js"></script> + <script type="text/javascript" src="../wasm_exec.js"></script> + <script> + // Use the encoder to convert a string to Uint8Array using enc.encode() + + const go = new Go(); + WebAssembly.instantiateStreaming(fetch("../xxdk.wasm"), go.importObject).then((result) => { + go.run(result.instance); + + LogLevel(0); + + const recipientContactFile = ''; + const myContactFileName = 'myE2eContact.xxc'; + const statePath = 'statePathRecipient'; + const statePass = 'password'; + + document.getElementById('ndf-input') + .addEventListener('change', function (e) { + let reader = new FileReader(); + reader.onload = function (e) { + SendE2e(e.target.result, recipientContactFile, myContactFileName, statePath, statePass); + }; + reader.readAsText(e.target.files[0]); + }, false); + }); + </script> +</head> +<body> +<form id="start-cmix"> + <div> + <label for="ndf-input">Select NDF file to use <code>ndf</code> variable in JS (required):</label><br/> + <input type="file" id="ndf-input" required/> + </div> + <!-- <div> + <label for="recipient-contact-input">Recipient contact path (optional):</label><br/> + <input type="file" id="recipient-contact-input"/> + </div> + <div> + <label for="session-path">Session path (required):</label><br/> + <input type="text" id="session-path" required/> + </div> + <div> + <label for="session-password">Storage password (required):</label><br/> + <input type="text" id="session-password" required/> + </div> + <input type="submit" value="Start">--> +</form> +<div id="output" style="padding:10px;border:1px solid red"></div> +</body> + +<script> +</script> +</html> \ No newline at end of file diff --git a/examples/sendE2E/index2.html b/examples/sendE2E/index2.html new file mode 100644 index 00000000..72443bc5 --- /dev/null +++ b/examples/sendE2E/index2.html @@ -0,0 +1,59 @@ +<!-- + ~ Copyright © 2020 xx network SEZC /// + ~ /// + ~ Use of this source code is governed by a license that can be found in the /// + ~ LICENSE file /// + --> + +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <title>xxDk WebAssembly Test</title> + + <link rel="stylesheet" type="text/css" href="../styles.css"> + <script type="text/javascript" src="xxdk.js"></script> + <script type="text/javascript" src="../wasm_exec.js"></script> + <script> + // Use the encoder to convert a string to Uint8Array using enc.encode() + + const go = new Go(); + WebAssembly.instantiateStreaming(fetch("../xxdk.wasm"), go.importObject).then((result) => { + go.run(result.instance); + + LogLevel(0); + + const recipientContactFile = '<xxc(2)qM22QBlbReXsTQ+zqDWr0ZSh7sFkDRjxqprUHyWe3aUDrgZ7Ugdw/BAr6cY/smgr1a48u+tl0EStWz84jY/oP+2BuftqQ5sW4gRxRsIHR+SG2lQb/NWIZt763UWGFTQAgCMCuSlhX3hVec972BhVY4igzVIazFbaDRZFONT8aeOSHIJFOgSQsFcfLX7s+ZZDSXVyx+4kZduyMRB1EhwtF4z4a5zebmoA9DB9H4B/jXB1H0pvkLXVS8QtWqtSP/9BHvSipXDXtnxlKxTwBaw6xNTWZnCY71nznRe2BElQrkLR7SyLWl3cMXeby2axbLO8nxsx5L9NKwvZOGlhDSlmpSfGaSoU8ETQ9F3LFj+wQ/xMrTR1bb0hUwkRkrLL4gmH9n0dNtZ/EblonRd7+v/lJnYHPCZCAPBMZQ9upqwJ7ZafgJkoP1dX/LT/mHoLB3Z8zeKvIXFC2IpT2notYxfMv1PRpqMna+FEK71dj/UVPxKS9eIOv7KnmUmOEWRQ66OB9MdKiINKMqik0zsTn3jSMj6OyePo2wshWcozHE/VXkvrHHhYVxFMjaN0Ysr2YYHQ/JGCNFkHsfHWQAAAAgA7j/wRIx1q2PjH8zBC6s8kmw==xxc>'; + const myContactFileName = 'theirE2eContact.xxc'; + const statePath = 'statePathSender'; + const statePass = 'password'; + + document.getElementById('ndf-input') + .addEventListener('change', function (e) { + let reader = new FileReader(); + reader.onload = function (e) { + SendE2e(e.target.result, recipientContactFile, myContactFileName, statePath, statePass); + }; + reader.readAsText(e.target.files[0]); + }, false); + }); + </script> +</head> +<body> +<form id="start-cmix"> + <div> + <label for="ndf-input">Select NDF file to use <code>ndf</code> variable in JS:</label><br/> + <input type="file" id="ndf-input" required/> + </div> +<!-- <div> + <label for="ndf-input">Recipient contact path:</label><br/> + <input type="file" id="recipient-contact-input" required/> + </div> + <input type="submit" value="Start">--> +</form> +<div id="output" style="padding:10px;border:1px solid red"></div> +</body> + +<script> +</script> +</html> \ No newline at end of file diff --git a/examples/sendE2E/xxdk.js b/examples/sendE2E/xxdk.js new file mode 100644 index 00000000..9a0840c0 --- /dev/null +++ b/examples/sendE2E/xxdk.js @@ -0,0 +1,225 @@ +/* + * Copyright © 2020 xx network SEZC /// + * /// + * Use of this source code is governed by a license that can be found in the /// + * LICENSE file /// + */ + +async function SendE2e(ndf, recipientContactFile, myContactFileName, statePath, statePassString) { + let enc = new TextEncoder(); + let dec = new TextDecoder(); + + const output = document.getElementById("output") + const statePass = enc.encode(statePassString); + + // Check if state exists + if (localStorage.getItem(statePath) === null) { + console.log('getting key ' + statePath + ' returned null; making new cmix'); + + output.innerHTML += "Loading new storage\n" + + // Initialize the state + NewCmix(ndf, statePath, statePass, ''); + } + + + //////////////////////////////////////////////////////////////////////////// + // Login to your client session // + //////////////////////////////////////////////////////////////////////////// + + // Login with the same statePath and statePass used to call NewCmix + let netID = LoadCmix(statePath, statePass, GetDefaultCMixParams()); + console.log("LoadCmix() " + netID) + + console.log("sleep start") + await sleep(3000) + console.log("sleep end") + + let net = GetLoadCmix(netID) + console.log("loaded cmix: " + net) + + // Get reception identity (automatically created if one does not exist) + const identityStorageKey = "identityStorageKey"; + let identity; + try { + identity = LoadReceptionIdentity(identityStorageKey, net.GetID()); + } catch { + // If no extant xxdk.ReceptionIdentity, generate and store a new one + identity = net.MakeReceptionIdentity(); + + StoreReceptionIdentity(identityStorageKey, identity, net.GetID()); + } + + // Print contact to console. This should probably save a file. + const myContactFile = dec.decode(GetContactFromReceptionIdentity(identity)) + console.log("my contact file content: " + myContactFile); + + // Start file download. + download(myContactFileName, myContactFile); + + let confirm = false; + let confirmContact; + let authCallbacks = { + Confirm: function (contact, receptionId, ephemeralId, roundId) { + confirm = true; + confirmContact = contact + console.log("Confirm:"); + console.log("contact: " + dec.decode(contact)); + console.log("receptionId: " + ephemeralId.toString()); + console.log("ephemeralId: " + roundId.toString()); + + output.innerHTML += "Received confirmation from " + ephemeralId.toString() + "<br />" + } + } + + // Create an E2E client + // Pass in auth object which controls auth callbacks for this client + const params = GetDefaultE2EParams(); + console.log("Using E2E parameters: " + dec.decode(params)); + let e2eClient = Login(net.GetID(), authCallbacks, identity, params); + + + //////////////////////////////////////////////////////////////////////////// + // Start network threads // + //////////////////////////////////////////////////////////////////////////// + + // Set networkFollowerTimeout to a value of your choice (seconds) + net.StartNetworkFollower(5000); + + output.innerHTML += "Starting network follower<br />" + + // Set up a wait for the network to be connected + let health = false + const n = 100 + let myPromise = new Promise(async function (myResolve, myReject) { + for (let i = 0; (health === false) && (i < n); i++) { + await sleep(100) + } + if (health === true) { + myResolve("OK"); + } else { + myReject("timed out waiting for healthy network"); + } + }); + + // Provide a callback that will be signalled when network health status changes + net.AddHealthCallback({ + Callback: function (healthy) { + health = healthy; + } + }); + await sleep(3000) + + // Wait until connected or crash on timeout + myPromise.then( + function (value) { + output.innerHTML += "Network is healthy<br />" + console.log("network is healthy") + }, + function (error) { + output.innerHTML += "Network is not healthy<br />" + // throw error; + } + ); + + + //////////////////////////////////////////////////////////////////////////// + // Register a listener for messages // + //////////////////////////////////////////////////////////////////////////// + + let listener = { + Hear: function (item) { + console.log("Listener heard: " + dec.decode(item)); + output.innerHTML += "Listener heard: " + dec.decode(item) + "<br />" + }, + Name: function () { + return "Listener"; + } + } + + // Listen for all types of messages using catalog.NoType + // Listen for messages from all users using id.ZeroUser + let zerUser = Uint8Array.from([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3]); + e2eClient.RegisterListener(zerUser, 0, listener); + + output.innerHTML += "Registered listener<br />" + + //////////////////////////////////////////////////////////////////////////// + // Connect with the recipient // + //////////////////////////////////////////////////////////////////////////// + + // Check that the partner exists + if (recipientContactFile !== '') { + let exists = false; + output.innerHTML += "getting ID from contact<br />" + const recipientContactID = GetIDFromContact(recipientContactFile); + output.innerHTML += "Checking for " + btoa(recipientContactID) + "<br />" + let partners = e2eClient.GetAllPartnerIDs(); + + for (let i = 0; i < partners.length; i++) { + + if (partners[i] === recipientContactID) { + console.log("partner " + btoa(recipientContactID) + " matches partner " + i + " " + btoa(partners[i])) + exists = true; + break + } + } + + // If the partner does not exist, send a request + if (exists === false) { + output.innerHTML += "Request sent to " + btoa(recipientContactID) + "<br />" + const factList = enc.encode('[]') + e2eClient.Request(enc.encode(recipientContactFile), factList) + + for (let i = 0; (i < 600) && (confirm === false); i++) { + await sleep(50) + } + if (confirm === false) { + output.innerHTML += "Checking for " + recipientContactID + "<br />" + throw new Error("timed out waiting for confirmation") + } + + const confirmContactID = GetIDFromContact(confirmContact) + if (recipientContactID !== confirmContactID) { + throw new Error("contact ID from confirmation " + + btoa(dec.decode(confirmContactID)) + + " does not match recipient ID " + + btoa(dec.decode(recipientContactID))) + } + } + + //////////////////////////////////////////////////////////////////////////// + // Send a message to the recipient // + //////////////////////////////////////////////////////////////////////////// + + // Test message + const msgBody = "If this message is sent successfully, we'll have established contact with the recipient." + + output.innerHTML += "Sending E2E message<br />" + const paramsObj = JSON.parse(dec.decode(params)) + const e2eSendReport = e2eClient.SendE2E(2, recipientContactID, enc.encode(msgBody), enc.encode(JSON.stringify(paramsObj.Base))) + + console.log("e2e send report: " + dec.decode(e2eSendReport)) + output.innerHTML += "Send e2e: " + dec.decode(e2eSendReport) + "<br />" + } else { + output.innerHTML += "Partner does not exist<br />" + } +} + +function sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +function download(filename, text) { + let element = document.createElement('a'); + element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text)); + element.setAttribute('download', filename); + + element.style.display = 'none'; + document.body.appendChild(element); + + element.click(); + + document.body.removeChild(element); +} diff --git a/examples/styles.css b/examples/styles.css new file mode 100644 index 00000000..802a6a38 --- /dev/null +++ b/examples/styles.css @@ -0,0 +1,82 @@ +/* + * Copyright © 2020 xx network SEZC /// + * /// + * Use of this source code is governed by a license that can be found in the /// + * LICENSE file /// + */ + +/****************************************************************************** + * Copyright © 2022 xx foundation * + * * + * Use of this source code is governed by a license that can be found in the * + * LICENSE file. * + ******************************************************************************/ + +/****************************************************************************** + * CSS Reset (meyerweb reset, v2.0 | 20110126) * + ******************************************************************************/ + +html, body, div, span, applet, object, iframe, +h1, h2, h3, h4, h5, h6, p, blockquote, pre, +a, abbr, acronym, address, big, cite, code, +del, dfn, em, img, ins, kbd, q, s, samp, +small, strike, strong, sub, sup, tt, var, +b, u, i, center, +dl, dt, dd, ol, ul, li, +fieldset, form, label, legend, +table, caption, tbody, tfoot, thead, tr, th, td, +article, aside, canvas, details, embed, +figure, figcaption, footer, header, hgroup, +menu, nav, output, ruby, section, summary, +time, mark, audio, video { + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + font: inherit; + vertical-align: baseline; +} + +/* HTML5 display-role reset for older browsers */ +article, aside, details, figcaption, figure, +footer, header, hgroup, menu, nav, section { + display: block; +} + +body { + line-height: 1; +} + +ol, ul { + list-style: none; +} + +blockquote, q { + quotes: none; +} + +blockquote:before, blockquote:after, +q:before, q:after { + content: ''; + content: none; +} + +table { + border-collapse: collapse; + border-spacing: 0; +} + +/****************************************************************************** + * Global Styles * + ******************************************************************************/ +body { + font-family: "Roboto", "Franklin Gothic Medium", Tahoma, sans-serif; +} + +form { + margin:1em; +} + +form div { + margin:1em 0; +} \ No newline at end of file diff --git a/examples/wasm_exec.js b/examples/wasm_exec.js new file mode 100644 index 00000000..634d5628 --- /dev/null +++ b/examples/wasm_exec.js @@ -0,0 +1,650 @@ +/* + * Copyright © 2020 xx network SEZC /// + * /// + * Use of this source code is governed by a license that can be found in the /// + * LICENSE file /// + */ + +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +(() => { + // Map multiple JavaScript environments to a single common API, + // preferring web standards over Node.js API. + // + // Environments considered: + // - Browsers + // - Node.js + // - Electron + // - Parcel + // - Webpack + + if (typeof global !== "undefined") { + // global already exists + } else if (typeof window !== "undefined") { + window.global = window; + } else if (typeof self !== "undefined") { + self.global = self; + } else { + throw new Error("cannot export Go (neither global, window nor self is defined)"); + } + + if (!global.require && typeof require !== "undefined") { + global.require = require; + } + + if (!global.fs && global.require) { + const fs = require("fs"); + if (typeof fs === "object" && fs !== null && Object.keys(fs).length !== 0) { + global.fs = fs; + } + } + + const enosys = () => { + const err = new Error("not implemented"); + err.code = "ENOSYS"; + return err; + }; + + if (!global.fs) { + let outputBuf = ""; + global.fs = { + constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused + writeSync(fd, buf) { + outputBuf += decoder.decode(buf); + const nl = outputBuf.lastIndexOf("\n"); + if (nl != -1) { + console.log(outputBuf.substr(0, nl)); + outputBuf = outputBuf.substr(nl + 1); + } + return buf.length; + }, + write(fd, buf, offset, length, position, callback) { + if (offset !== 0 || length !== buf.length || position !== null) { + callback(enosys()); + return; + } + const n = this.writeSync(fd, buf); + callback(null, n); + }, + chmod(path, mode, callback) { callback(enosys()); }, + chown(path, uid, gid, callback) { callback(enosys()); }, + close(fd, callback) { callback(enosys()); }, + fchmod(fd, mode, callback) { callback(enosys()); }, + fchown(fd, uid, gid, callback) { callback(enosys()); }, + fstat(fd, callback) { callback(enosys()); }, + fsync(fd, callback) { callback(null); }, + ftruncate(fd, length, callback) { callback(enosys()); }, + lchown(path, uid, gid, callback) { callback(enosys()); }, + link(path, link, callback) { callback(enosys()); }, + lstat(path, callback) { callback(enosys()); }, + mkdir(path, perm, callback) { callback(enosys()); }, + open(path, flags, mode, callback) { callback(enosys()); }, + read(fd, buffer, offset, length, position, callback) { callback(enosys()); }, + readdir(path, callback) { callback(enosys()); }, + readlink(path, callback) { callback(enosys()); }, + rename(from, to, callback) { callback(enosys()); }, + rmdir(path, callback) { callback(enosys()); }, + stat(path, callback) { callback(enosys()); }, + symlink(path, link, callback) { callback(enosys()); }, + truncate(path, length, callback) { callback(enosys()); }, + unlink(path, callback) { callback(enosys()); }, + utimes(path, atime, mtime, callback) { callback(enosys()); }, + }; + } + + if (!global.process) { + global.process = { + getuid() { return -1; }, + getgid() { return -1; }, + geteuid() { return -1; }, + getegid() { return -1; }, + getgroups() { throw enosys(); }, + pid: -1, + ppid: -1, + umask() { throw enosys(); }, + cwd() { throw enosys(); }, + chdir() { throw enosys(); }, + } + } + + if (!global.crypto && global.require) { + const nodeCrypto = require("crypto"); + global.crypto = { + getRandomValues(b) { + nodeCrypto.randomFillSync(b); + }, + }; + } + if (!global.crypto) { + throw new Error("global.crypto is not available, polyfill required (getRandomValues only)"); + } + + if (!global.performance) { + global.performance = { + now() { + const [sec, nsec] = process.hrtime(); + return sec * 1000 + nsec / 1000000; + }, + }; + } + + if (!global.TextEncoder && global.require) { + global.TextEncoder = require("util").TextEncoder; + } + if (!global.TextEncoder) { + throw new Error("global.TextEncoder is not available, polyfill required"); + } + + if (!global.TextDecoder && global.require) { + global.TextDecoder = require("util").TextDecoder; + } + if (!global.TextDecoder) { + throw new Error("global.TextDecoder is not available, polyfill required"); + } + + // End of polyfills for common API. + + const encoder = new TextEncoder("utf-8"); + const decoder = new TextDecoder("utf-8"); + + global.Go = class { + constructor() { + this.argv = ["js"]; + this.env = {}; + this.exit = (code) => { + if (code !== 0) { + console.warn("exit code:", code); + } + }; + this._exitPromise = new Promise((resolve) => { + this._resolveExitPromise = resolve; + }); + this._pendingEvent = null; + this._scheduledTimeouts = new Map(); + this._nextCallbackTimeoutID = 1; + + const setInt64 = (addr, v) => { + this.mem.setUint32(addr + 0, v, true); + this.mem.setUint32(addr + 4, Math.floor(v / 4294967296), true); + } + + const getInt64 = (addr) => { + const low = this.mem.getUint32(addr + 0, true); + const high = this.mem.getInt32(addr + 4, true); + return low + high * 4294967296; + } + + const loadValue = (addr) => { + const f = this.mem.getFloat64(addr, true); + if (f === 0) { + return undefined; + } + if (!isNaN(f)) { + return f; + } + + const id = this.mem.getUint32(addr, true); + return this._values[id]; + } + + const storeValue = (addr, v) => { + const nanHead = 0x7FF80000; + + if (typeof v === "number" && v !== 0) { + if (isNaN(v)) { + this.mem.setUint32(addr + 4, nanHead, true); + this.mem.setUint32(addr, 0, true); + return; + } + this.mem.setFloat64(addr, v, true); + return; + } + + if (v === undefined) { + this.mem.setFloat64(addr, 0, true); + return; + } + + let id = this._ids.get(v); + if (id === undefined) { + id = this._idPool.pop(); + if (id === undefined) { + id = this._values.length; + } + this._values[id] = v; + this._goRefCounts[id] = 0; + this._ids.set(v, id); + } + this._goRefCounts[id]++; + let typeFlag = 0; + switch (typeof v) { + case "object": + if (v !== null) { + typeFlag = 1; + } + break; + case "string": + typeFlag = 2; + break; + case "symbol": + typeFlag = 3; + break; + case "function": + typeFlag = 4; + break; + } + this.mem.setUint32(addr + 4, nanHead | typeFlag, true); + this.mem.setUint32(addr, id, true); + } + + const loadSlice = (addr) => { + const array = getInt64(addr + 0); + const len = getInt64(addr + 8); + return new Uint8Array(this._inst.exports.mem.buffer, array, len); + } + + const loadSliceOfValues = (addr) => { + const array = getInt64(addr + 0); + const len = getInt64(addr + 8); + const a = new Array(len); + for (let i = 0; i < len; i++) { + a[i] = loadValue(array + i * 8); + } + return a; + } + + const loadString = (addr) => { + const saddr = getInt64(addr + 0); + const len = getInt64(addr + 8); + return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len)); + } + + const timeOrigin = Date.now() - performance.now(); + this.importObject = { + go: { + // Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters) + // may synchronously trigger a Go event handler. This makes Go code get executed in the middle of the imported + // function. A goroutine can switch to a new stack if the current stack is too small (see morestack function). + // This changes the SP, thus we have to update the SP used by the imported function. + + // func wasmExit(code int32) + "runtime.wasmExit": (sp) => { + sp >>>= 0; + const code = this.mem.getInt32(sp + 8, true); + this.exited = true; + delete this._inst; + delete this._values; + delete this._goRefCounts; + delete this._ids; + delete this._idPool; + this.exit(code); + }, + + // func wasmWrite(fd uintptr, p unsafe.Pointer, n int32) + "runtime.wasmWrite": (sp) => { + sp >>>= 0; + const fd = getInt64(sp + 8); + const p = getInt64(sp + 16); + const n = this.mem.getInt32(sp + 24, true); + fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n)); + }, + + // func resetMemoryDataView() + "runtime.resetMemoryDataView": (sp) => { + sp >>>= 0; + this.mem = new DataView(this._inst.exports.mem.buffer); + }, + + // func nanotime1() int64 + "runtime.nanotime1": (sp) => { + sp >>>= 0; + setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000); + }, + + // func walltime() (sec int64, nsec int32) + "runtime.walltime": (sp) => { + sp >>>= 0; + const msec = (new Date).getTime(); + setInt64(sp + 8, msec / 1000); + this.mem.setInt32(sp + 16, (msec % 1000) * 1000000, true); + }, + + // func scheduleTimeoutEvent(delay int64) int32 + "runtime.scheduleTimeoutEvent": (sp) => { + sp >>>= 0; + const id = this._nextCallbackTimeoutID; + this._nextCallbackTimeoutID++; + this._scheduledTimeouts.set(id, setTimeout( + () => { + this._resume(); + while (this._scheduledTimeouts.has(id)) { + // for some reason Go failed to register the timeout event, log and try again + // (temporary workaround for https://github.com/golang/go/issues/28975) + console.warn("scheduleTimeoutEvent: missed timeout event"); + this._resume(); + } + }, + getInt64(sp + 8) + 1, // setTimeout has been seen to fire up to 1 millisecond early + )); + this.mem.setInt32(sp + 16, id, true); + }, + + // func clearTimeoutEvent(id int32) + "runtime.clearTimeoutEvent": (sp) => { + sp >>>= 0; + const id = this.mem.getInt32(sp + 8, true); + clearTimeout(this._scheduledTimeouts.get(id)); + this._scheduledTimeouts.delete(id); + }, + + // func getRandomData(r []byte) + "runtime.getRandomData": (sp) => { + sp >>>= 0; + crypto.getRandomValues(loadSlice(sp + 8)); + }, + + // func finalizeRef(v ref) + "syscall/js.finalizeRef": (sp) => { + sp >>>= 0; + const id = this.mem.getUint32(sp + 8, true); + this._goRefCounts[id]--; + if (this._goRefCounts[id] === 0) { + const v = this._values[id]; + this._values[id] = null; + this._ids.delete(v); + this._idPool.push(id); + } + }, + + // func stringVal(value string) ref + "syscall/js.stringVal": (sp) => { + sp >>>= 0; + storeValue(sp + 24, loadString(sp + 8)); + }, + + // func valueGet(v ref, p string) ref + "syscall/js.valueGet": (sp) => { + sp >>>= 0; + const result = Reflect.get(loadValue(sp + 8), loadString(sp + 16)); + sp = this._inst.exports.getsp() >>> 0; // see comment above + storeValue(sp + 32, result); + }, + + // func valueSet(v ref, p string, x ref) + "syscall/js.valueSet": (sp) => { + sp >>>= 0; + Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32)); + }, + + // func valueDelete(v ref, p string) + "syscall/js.valueDelete": (sp) => { + sp >>>= 0; + Reflect.deleteProperty(loadValue(sp + 8), loadString(sp + 16)); + }, + + // func valueIndex(v ref, i int) ref + "syscall/js.valueIndex": (sp) => { + sp >>>= 0; + storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16))); + }, + + // valueSetIndex(v ref, i int, x ref) + "syscall/js.valueSetIndex": (sp) => { + sp >>>= 0; + Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24)); + }, + + // func valueCall(v ref, m string, args []ref) (ref, bool) + "syscall/js.valueCall": (sp) => { + sp >>>= 0; + try { + const v = loadValue(sp + 8); + const m = Reflect.get(v, loadString(sp + 16)); + const args = loadSliceOfValues(sp + 32); + const result = Reflect.apply(m, v, args); + sp = this._inst.exports.getsp() >>> 0; // see comment above + storeValue(sp + 56, result); + this.mem.setUint8(sp + 64, 1); + } catch (err) { + sp = this._inst.exports.getsp() >>> 0; // see comment above + storeValue(sp + 56, err); + this.mem.setUint8(sp + 64, 0); + } + }, + + // func valueInvoke(v ref, args []ref) (ref, bool) + "syscall/js.valueInvoke": (sp) => { + sp >>>= 0; + try { + const v = loadValue(sp + 8); + const args = loadSliceOfValues(sp + 16); + const result = Reflect.apply(v, undefined, args); + sp = this._inst.exports.getsp() >>> 0; // see comment above + storeValue(sp + 40, result); + this.mem.setUint8(sp + 48, 1); + } catch (err) { + sp = this._inst.exports.getsp() >>> 0; // see comment above + storeValue(sp + 40, err); + this.mem.setUint8(sp + 48, 0); + } + }, + + // func valueNew(v ref, args []ref) (ref, bool) + "syscall/js.valueNew": (sp) => { + sp >>>= 0; + try { + const v = loadValue(sp + 8); + const args = loadSliceOfValues(sp + 16); + const result = Reflect.construct(v, args); + sp = this._inst.exports.getsp() >>> 0; // see comment above + storeValue(sp + 40, result); + this.mem.setUint8(sp + 48, 1); + } catch (err) { + sp = this._inst.exports.getsp() >>> 0; // see comment above + storeValue(sp + 40, err); + this.mem.setUint8(sp + 48, 0); + } + }, + + // func valueLength(v ref) int + "syscall/js.valueLength": (sp) => { + sp >>>= 0; + setInt64(sp + 16, parseInt(loadValue(sp + 8).length)); + }, + + // valuePrepareString(v ref) (ref, int) + "syscall/js.valuePrepareString": (sp) => { + sp >>>= 0; + const str = encoder.encode(String(loadValue(sp + 8))); + storeValue(sp + 16, str); + setInt64(sp + 24, str.length); + }, + + // valueLoadString(v ref, b []byte) + "syscall/js.valueLoadString": (sp) => { + sp >>>= 0; + const str = loadValue(sp + 8); + loadSlice(sp + 16).set(str); + }, + + // func valueInstanceOf(v ref, t ref) bool + "syscall/js.valueInstanceOf": (sp) => { + sp >>>= 0; + this.mem.setUint8(sp + 24, (loadValue(sp + 8) instanceof loadValue(sp + 16)) ? 1 : 0); + }, + + // func copyBytesToGo(dst []byte, src ref) (int, bool) + "syscall/js.copyBytesToGo": (sp) => { + sp >>>= 0; + const dst = loadSlice(sp + 8); + const src = loadValue(sp + 32); + if (!(src instanceof Uint8Array || src instanceof Uint8ClampedArray)) { + this.mem.setUint8(sp + 48, 0); + return; + } + const toCopy = src.subarray(0, dst.length); + dst.set(toCopy); + setInt64(sp + 40, toCopy.length); + this.mem.setUint8(sp + 48, 1); + }, + + // func copyBytesToJS(dst ref, src []byte) (int, bool) + "syscall/js.copyBytesToJS": (sp) => { + sp >>>= 0; + const dst = loadValue(sp + 8); + const src = loadSlice(sp + 16); + if (!(dst instanceof Uint8Array || dst instanceof Uint8ClampedArray)) { + this.mem.setUint8(sp + 48, 0); + return; + } + const toCopy = src.subarray(0, dst.length); + dst.set(toCopy); + setInt64(sp + 40, toCopy.length); + this.mem.setUint8(sp + 48, 1); + }, + + "debug": (value) => { + console.log(value); + }, + + // func throw(exception string, message string) + 'gitlab.com/elixxir/xxdk-wasm/wasm.throw': (sp) => { + const exception = loadString(sp + 8) + const message = loadString(sp + 24) + throw globalThis[exception](message) + }, + } + }; + } + + async run(instance) { + if (!(instance instanceof WebAssembly.Instance)) { + throw new Error("Go.run: WebAssembly.Instance expected"); + } + this._inst = instance; + this.mem = new DataView(this._inst.exports.mem.buffer); + this._values = [ // JS values that Go currently has references to, indexed by reference id + NaN, + 0, + null, + true, + false, + global, + this, + ]; + this._goRefCounts = new Array(this._values.length).fill(Infinity); // number of references that Go has to a JS value, indexed by reference id + this._ids = new Map([ // mapping from JS values to reference ids + [0, 1], + [null, 2], + [true, 3], + [false, 4], + [global, 5], + [this, 6], + ]); + this._idPool = []; // unused ids that have been garbage collected + this.exited = false; // whether the Go program has exited + + // Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory. + let offset = 4096; + + const strPtr = (str) => { + const ptr = offset; + const bytes = encoder.encode(str + "\0"); + new Uint8Array(this.mem.buffer, offset, bytes.length).set(bytes); + offset += bytes.length; + if (offset % 8 !== 0) { + offset += 8 - (offset % 8); + } + return ptr; + }; + + const argc = this.argv.length; + + const argvPtrs = []; + this.argv.forEach((arg) => { + argvPtrs.push(strPtr(arg)); + }); + argvPtrs.push(0); + + const keys = Object.keys(this.env).sort(); + keys.forEach((key) => { + argvPtrs.push(strPtr(`${key}=${this.env[key]}`)); + }); + argvPtrs.push(0); + + const argv = offset; + argvPtrs.forEach((ptr) => { + this.mem.setUint32(offset, ptr, true); + this.mem.setUint32(offset + 4, 0, true); + offset += 8; + }); + + // The linker guarantees global data starts from at least wasmMinDataAddr. + // Keep in sync with cmd/link/internal/ld/data.go:wasmMinDataAddr. + const wasmMinDataAddr = 4096 + 8192; + if (offset >= wasmMinDataAddr) { + throw new Error("total length of command line and environment variables exceeds limit"); + } + + this._inst.exports.run(argc, argv); + if (this.exited) { + this._resolveExitPromise(); + } + await this._exitPromise; + } + + _resume() { + if (this.exited) { + throw new Error("Go program has already exited"); + } + this._inst.exports.resume(); + if (this.exited) { + this._resolveExitPromise(); + } + } + + _makeFuncWrapper(id) { + const go = this; + return function () { + const event = { id: id, this: this, args: arguments }; + go._pendingEvent = event; + go._resume(); + return event.result; + }; + } + } + + if ( + typeof module !== "undefined" && + global.require && + global.require.main === module && + global.process && + global.process.versions && + !global.process.versions.electron + ) { + if (process.argv.length < 3) { + console.error("usage: go_js_wasm_exec [wasm binary] [arguments]"); + process.exit(1); + } + + const go = new Go(); + go.argv = process.argv.slice(2); + go.env = Object.assign({ TMPDIR: require("os").tmpdir() }, process.env); + go.exit = process.exit; + WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then((result) => { + process.on("exit", (code) => { // Node.js exits if no event handler is pending + if (code === 0 && !go.exited) { + // deadlock, make Go print error and stack traces + go._pendingEvent = { id: 0 }; + go._resume(); + } + }); + return go.run(result.instance); + }).catch((err) => { + console.error(err); + process.exit(1); + }); + } +})(); diff --git a/main.go b/main.go index 3f38c3ed..28fcf234 100644 --- a/main.go +++ b/main.go @@ -11,6 +11,8 @@ package main import ( "fmt" + "github.com/pkg/errors" + jww "github.com/spf13/jwalterweatherman" "gitlab.com/elixxir/client/bindings" "gitlab.com/elixxir/xxdk-wasm/wasm" "os" @@ -32,6 +34,7 @@ func main() { // wasm/cmix.go js.Global().Set("NewCmix", js.FuncOf(wasm.NewCmix)) js.Global().Set("LoadCmix", js.FuncOf(wasm.LoadCmix)) + js.Global().Set("GetLoadCmix", js.FuncOf(wasm.GetLoadCmix)) // wasm/dummy.go js.Global().Set("NewDummyTrafficManager", @@ -119,6 +122,14 @@ func main() { js.Global().Set("GetGitVersion", js.FuncOf(wasm.GetGitVersion)) js.Global().Set("GetDependencies", js.FuncOf(wasm.GetDependencies)) + defer func() { + jww.CRITICAL.Printf("Before recover\n") + if rec := recover(); rec != nil { + wasm.Throw(wasm.TypeError, errors.Errorf(fmt.Sprintf("%+v", rec))) + } + jww.CRITICAL.Printf("After recover\n") + }() + <-make(chan bool) os.Exit(0) } diff --git a/wasm/cmix.go b/wasm/cmix.go index 1908b7f6..6d410574 100644 --- a/wasm/cmix.go +++ b/wasm/cmix.go @@ -108,13 +108,25 @@ func LoadCmix(_ js.Value, args []js.Value) interface{} { password := CopyBytesToGo(args[1]) cmixParamsJSON := CopyBytesToGo(args[2]) - net, err := bindings.LoadCmix(args[0].String(), password, cmixParamsJSON) - if err != nil { - Throw(TypeError, err) - return nil - } + go func() { + _, err := bindings.LoadCmix(args[0].String(), password, cmixParamsJSON) + if err != nil { + Throw(TypeError, err) + } + }() + + return bindings.GetCurrentID() +} - return newCmixJS(net) +// GetLoadCmix returns the cmix object for the ID. +// +// Parameters: +// - args[0] - ID of Cmix object in tracker (int). +// +// Returns: +// - Javascript representation of the Cmix object +func GetLoadCmix(_ js.Value, args []js.Value) interface{} { + return newCmixJS(bindings.GetCmix(args[0].Int())) } // GetID returns the ID for this [bindings.Cmix] in the cmixTracker. diff --git a/wasm/e2eHandler.go b/wasm/e2eHandler.go index 57d16f89..ed90c0f9 100644 --- a/wasm/e2eHandler.go +++ b/wasm/e2eHandler.go @@ -10,6 +10,7 @@ package wasm import ( + jww "github.com/spf13/jwalterweatherman" "syscall/js" ) @@ -157,14 +158,18 @@ func (e *E2e) SendE2E(_ js.Value, args []js.Value) interface{} { payload := CopyBytesToGo(args[2]) e2eParams := CopyBytesToGo(args[3]) - sendReport, err := e.api.SendE2E( - args[0].Int(), recipientId, payload, e2eParams) - if err != nil { - Throw(TypeError, err) - return nil - } + go func() { + sendReport, err := e.api.SendE2E( + args[0].Int(), recipientId, payload, e2eParams) + if err != nil { + Throw(TypeError, err) + } + + jww.INFO.Printf("Send report: %+v", sendReport) + }() - return CopyBytesToJS(sendReport) + // return CopyBytesToJS(sendReport) + return nil } // processor wraps Javascript callbacks to adhere to the [bindings.Processor] -- GitLab