neuronum 8.0.0__tar.gz → 8.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of neuronum might be problematic. Click here for more details.
- {neuronum-8.0.0 → neuronum-8.2.0}/PKG-INFO +3 -5
- {neuronum-8.0.0 → neuronum-8.2.0}/README.md +2 -4
- {neuronum-8.0.0 → neuronum-8.2.0}/cli/main.py +88 -174
- neuronum-8.2.0/neuronum/__init__.py +1 -0
- {neuronum-8.0.0 → neuronum-8.2.0}/neuronum/neuronum.py +12 -141
- {neuronum-8.0.0 → neuronum-8.2.0}/neuronum.egg-info/PKG-INFO +3 -5
- {neuronum-8.0.0 → neuronum-8.2.0}/setup.py +1 -1
- neuronum-8.0.0/neuronum/__init__.py +0 -2
- {neuronum-8.0.0 → neuronum-8.2.0}/LICENSE.md +0 -0
- {neuronum-8.0.0 → neuronum-8.2.0}/cli/__init__.py +0 -0
- {neuronum-8.0.0 → neuronum-8.2.0}/neuronum.egg-info/SOURCES.txt +0 -0
- {neuronum-8.0.0 → neuronum-8.2.0}/neuronum.egg-info/dependency_links.txt +0 -0
- {neuronum-8.0.0 → neuronum-8.2.0}/neuronum.egg-info/entry_points.txt +0 -0
- {neuronum-8.0.0 → neuronum-8.2.0}/neuronum.egg-info/requires.txt +0 -0
- {neuronum-8.0.0 → neuronum-8.2.0}/neuronum.egg-info/top_level.txt +0 -0
- {neuronum-8.0.0 → neuronum-8.2.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: neuronum
|
|
3
|
-
Version: 8.
|
|
3
|
+
Version: 8.2.0
|
|
4
4
|
Summary: The E2E Web Engine
|
|
5
5
|
Home-page: https://neuronum.net
|
|
6
6
|
Author: Neuronum Cybernetics
|
|
@@ -71,7 +71,7 @@ Neuronum is a real-time web engine designed for developers to build E2E-native a
|
|
|
71
71
|
### **Tools & Features**
|
|
72
72
|
- Cell: Account to interact with Neuronum
|
|
73
73
|
- Nodes: Apps & Services built on Neuronum
|
|
74
|
-
- Browser:
|
|
74
|
+
- Browser: Web Browser built on Neuronum -> [build from source](https://github.com/neuronumcybernetics/neuronum_browser)
|
|
75
75
|
|
|
76
76
|
### Requirements
|
|
77
77
|
- Python >= 3.8
|
|
@@ -100,8 +100,6 @@ neuronum connect-cell
|
|
|
100
100
|
|
|
101
101
|
|
|
102
102
|
### **Build On Neuronum**
|
|
103
|
-
Visit & build with [Node Examples](https://github.com/neuronumcybernetics/neuronum/tree/main/features/nodes/examples) to gain deeper knowledge on how to build on Neuronum.
|
|
104
|
-
|
|
105
103
|
To get started, initialize a new Node with the command below.
|
|
106
104
|
```sh
|
|
107
105
|
neuronum init-node
|
|
@@ -123,4 +121,4 @@ neuronum start-node
|
|
|
123
121
|
|
|
124
122
|
### **Interact with your Node**
|
|
125
123
|
|
|
126
|
-
The **Neuronum Browser** is an open source web
|
|
124
|
+
The **Neuronum Browser** is an open source E2E web browser that allows you to interact with your nodes -> [build from source](https://github.com/neuronumcybernetics/neuronum_browser)
|
|
@@ -36,7 +36,7 @@ Neuronum is a real-time web engine designed for developers to build E2E-native a
|
|
|
36
36
|
### **Tools & Features**
|
|
37
37
|
- Cell: Account to interact with Neuronum
|
|
38
38
|
- Nodes: Apps & Services built on Neuronum
|
|
39
|
-
- Browser:
|
|
39
|
+
- Browser: Web Browser built on Neuronum -> [build from source](https://github.com/neuronumcybernetics/neuronum_browser)
|
|
40
40
|
|
|
41
41
|
### Requirements
|
|
42
42
|
- Python >= 3.8
|
|
@@ -65,8 +65,6 @@ neuronum connect-cell
|
|
|
65
65
|
|
|
66
66
|
|
|
67
67
|
### **Build On Neuronum**
|
|
68
|
-
Visit & build with [Node Examples](https://github.com/neuronumcybernetics/neuronum/tree/main/features/nodes/examples) to gain deeper knowledge on how to build on Neuronum.
|
|
69
|
-
|
|
70
68
|
To get started, initialize a new Node with the command below.
|
|
71
69
|
```sh
|
|
72
70
|
neuronum init-node
|
|
@@ -88,4 +86,4 @@ neuronum start-node
|
|
|
88
86
|
|
|
89
87
|
### **Interact with your Node**
|
|
90
88
|
|
|
91
|
-
The **Neuronum Browser** is an open source web
|
|
89
|
+
The **Neuronum Browser** is an open source E2E web browser that allows you to interact with your nodes -> [build from source](https://github.com/neuronumcybernetics/neuronum_browser)
|
|
@@ -340,21 +340,21 @@ async def async_init_node(descr):
|
|
|
340
340
|
app_path = project_path / "app.py"
|
|
341
341
|
app_path.write_text(f"""\
|
|
342
342
|
import asyncio
|
|
343
|
-
import
|
|
343
|
+
from neuronum import Node
|
|
344
344
|
from jinja2 import Environment, FileSystemLoader
|
|
345
345
|
|
|
346
346
|
env = Environment(loader=FileSystemLoader('.'))
|
|
347
347
|
template = env.get_template('ping.html')
|
|
348
348
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
349
|
+
node = Node(
|
|
350
|
+
id="{node_id}",
|
|
351
|
+
private_key="{private_key_file}",
|
|
352
|
+
public_key="{public_key_file}"
|
|
352
353
|
)
|
|
353
354
|
|
|
354
355
|
async def main():
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
async for transmitter in cell.sync(node_id):
|
|
356
|
+
|
|
357
|
+
async for transmitter in node.sync():
|
|
358
358
|
ts = transmitter.get("time")
|
|
359
359
|
data = transmitter.get("data")
|
|
360
360
|
transmitter_id = transmitter.get("transmitter_id")
|
|
@@ -373,7 +373,7 @@ async def main():
|
|
|
373
373
|
"html": html_content
|
|
374
374
|
}}
|
|
375
375
|
|
|
376
|
-
await
|
|
376
|
+
await node.tx_response(transmitter_id, response_data, client_public_key)
|
|
377
377
|
|
|
378
378
|
asyncio.run(main())
|
|
379
379
|
""")
|
|
@@ -534,7 +534,8 @@ f"""{{
|
|
|
534
534
|
"app_metadata": {{
|
|
535
535
|
"name": "{descr}",
|
|
536
536
|
"version": "1.0.0",
|
|
537
|
-
"author": "{host}"
|
|
537
|
+
"author": "{host}",
|
|
538
|
+
"audience": "private"
|
|
538
539
|
}},
|
|
539
540
|
"data_gateways": [
|
|
540
541
|
{{
|
|
@@ -815,107 +816,72 @@ async def async_stop_node():
|
|
|
815
816
|
@click.command()
|
|
816
817
|
def update_node():
|
|
817
818
|
click.echo("Update your Node")
|
|
818
|
-
credentials_folder_path = Path.home() / ".neuronum"
|
|
819
|
-
env_path = credentials_folder_path / ".env"
|
|
820
|
-
env_data = {}
|
|
821
|
-
|
|
822
819
|
try:
|
|
820
|
+
env_path = Path.home() / ".neuronum" / ".env"
|
|
821
|
+
env_data = {}
|
|
823
822
|
with open(env_path, "r") as f:
|
|
824
823
|
for line in f:
|
|
825
|
-
|
|
826
|
-
|
|
824
|
+
if "=" in line:
|
|
825
|
+
key, value = line.strip().split("=", 1)
|
|
826
|
+
env_data[key] = value
|
|
827
827
|
|
|
828
828
|
host = env_data.get("HOST", "")
|
|
829
829
|
|
|
830
|
-
|
|
831
|
-
|
|
830
|
+
with open("config.json", "r") as f:
|
|
831
|
+
config_data = json.load(f)
|
|
832
|
+
|
|
833
|
+
audience = config_data.get("app_metadata", {}).get("audience", "")
|
|
834
|
+
descr = config_data.get("app_metadata", {}).get("name", "")
|
|
835
|
+
|
|
836
|
+
if host.startswith("CMTY_") and audience != "private":
|
|
837
|
+
raise click.ClickException(
|
|
838
|
+
'Community Cells can only create private Nodes. Set audience to "private".'
|
|
839
|
+
)
|
|
840
|
+
if descr and len(descr) > 25:
|
|
841
|
+
raise click.ClickException(
|
|
842
|
+
'Description too long. Max 25 characters allowed.'
|
|
843
|
+
)
|
|
844
|
+
|
|
845
|
+
except FileNotFoundError as e:
|
|
846
|
+
click.echo(f"Error: File not found - {e.filename}")
|
|
832
847
|
return
|
|
833
|
-
except
|
|
834
|
-
click.echo(
|
|
848
|
+
except click.ClickException as e:
|
|
849
|
+
click.echo(e.format_message())
|
|
835
850
|
return
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
node_type = questionary.select(
|
|
839
|
-
"Community Cells can only create private Nodes",
|
|
840
|
-
choices=["private"]
|
|
841
|
-
).ask()
|
|
842
|
-
else:
|
|
843
|
-
node_type = questionary.select(
|
|
844
|
-
"Who can view your Node?:",
|
|
845
|
-
choices=["public", "private", "partners"]
|
|
846
|
-
).ask()
|
|
847
|
-
partners = "None"
|
|
848
|
-
if node_type == "partners":
|
|
849
|
-
prompt_msg = (
|
|
850
|
-
"Enter the list of partners who can view this Node.\n"
|
|
851
|
-
"Format: partner::cell, partner::cell, partner::cell\n"
|
|
852
|
-
"Press Enter to leave the list unchanged"
|
|
853
|
-
)
|
|
854
|
-
partners = click.prompt(
|
|
855
|
-
prompt_msg,
|
|
856
|
-
default="None",
|
|
857
|
-
show_default=False
|
|
858
|
-
).strip()
|
|
859
|
-
descr = click.prompt(
|
|
860
|
-
"Update Node description: Type up to 25 characters, or press Enter to leave it unchanged",
|
|
861
|
-
default="None",
|
|
862
|
-
show_default=False
|
|
863
|
-
).strip()
|
|
864
|
-
if descr and len(descr) > 25:
|
|
865
|
-
click.echo("Description too long. Max 25 characters allowed.")
|
|
851
|
+
except Exception as e:
|
|
852
|
+
click.echo(f"Error reading files: {e}")
|
|
866
853
|
return
|
|
867
|
-
asyncio.run(async_update_node(node_type, descr, partners))
|
|
868
854
|
|
|
869
|
-
|
|
870
|
-
credentials_folder_path = Path.home() / ".neuronum"
|
|
871
|
-
env_path = credentials_folder_path / ".env"
|
|
872
|
-
env_data = {}
|
|
855
|
+
asyncio.run(async_update_node(env_data, config_data, audience, descr))
|
|
873
856
|
|
|
874
|
-
try:
|
|
875
|
-
with open(env_path, "r") as f:
|
|
876
|
-
for line in f:
|
|
877
|
-
key, value = line.strip().split("=")
|
|
878
|
-
env_data[key] = value
|
|
879
857
|
|
|
858
|
+
async def async_update_node(env_data, config_data, audience: str, descr: str):
|
|
859
|
+
try:
|
|
880
860
|
host = env_data.get("HOST", "")
|
|
881
861
|
password = env_data.get("PASSWORD", "")
|
|
882
862
|
network = env_data.get("NETWORK", "")
|
|
883
863
|
synapse = env_data.get("SYNAPSE", "")
|
|
884
864
|
|
|
885
|
-
|
|
886
|
-
data = json.load(f)
|
|
887
|
-
|
|
888
|
-
nodeID = data['data_gateways'][0]['node_id']
|
|
889
|
-
|
|
890
|
-
except FileNotFoundError:
|
|
891
|
-
click.echo("Error: .env with credentials not found")
|
|
892
|
-
return
|
|
893
|
-
except Exception as e:
|
|
894
|
-
click.echo(f"Error reading .env file: {e}")
|
|
895
|
-
return
|
|
865
|
+
node_id = config_data.get("data_gateways", [{}])[0].get("node_id", "")
|
|
896
866
|
|
|
897
|
-
try:
|
|
898
867
|
with open("config.json", "r") as f:
|
|
899
|
-
|
|
868
|
+
config_file_content = f.read()
|
|
900
869
|
|
|
901
870
|
except FileNotFoundError:
|
|
902
|
-
click.echo("Error:
|
|
871
|
+
click.echo("Error: config.json or .env not found")
|
|
903
872
|
return
|
|
904
873
|
except Exception as e:
|
|
905
|
-
click.echo(f"Error reading
|
|
874
|
+
click.echo(f"Error reading files: {e}")
|
|
906
875
|
return
|
|
907
|
-
|
|
908
|
-
if node_type == "partners":
|
|
909
|
-
node_type = partners
|
|
910
876
|
|
|
911
877
|
url = f"https://{network}/api/update_node"
|
|
912
878
|
node = {
|
|
913
|
-
"nodeID":
|
|
879
|
+
"nodeID": node_id,
|
|
914
880
|
"host": host,
|
|
915
881
|
"password": password,
|
|
916
882
|
"synapse": synapse,
|
|
917
|
-
"node_type":
|
|
918
|
-
"config_file":
|
|
883
|
+
"node_type": audience,
|
|
884
|
+
"config_file": config_file_content,
|
|
919
885
|
"descr": descr,
|
|
920
886
|
}
|
|
921
887
|
|
|
@@ -924,120 +890,73 @@ async def async_update_node(node_type: str, descr: str, partners:str) -> None:
|
|
|
924
890
|
async with session.post(url, json=node) as response:
|
|
925
891
|
response.raise_for_status()
|
|
926
892
|
data = await response.json()
|
|
927
|
-
|
|
893
|
+
updated_node_id = data.get("nodeID", node_id)
|
|
894
|
+
click.echo(f"Neuronum Node '{updated_node_id}' updated!")
|
|
928
895
|
except aiohttp.ClientError as e:
|
|
929
896
|
click.echo(f"Error sending request: {e}")
|
|
930
|
-
return
|
|
931
897
|
|
|
932
|
-
if node_type == "public":
|
|
933
|
-
click.echo(f"Neuronum Node '{nodeID}' updated!")
|
|
934
|
-
else:
|
|
935
|
-
click.echo(f"Neuronum Node '{nodeID}' updated!")
|
|
936
898
|
|
|
937
899
|
|
|
938
900
|
def update_node_at_start():
|
|
939
901
|
click.echo("Update your Node")
|
|
940
|
-
credentials_folder_path = Path.home() / ".neuronum"
|
|
941
|
-
env_path = credentials_folder_path / ".env"
|
|
942
|
-
env_data = {}
|
|
943
|
-
|
|
944
902
|
try:
|
|
903
|
+
env_path = Path.home() / ".neuronum" / ".env"
|
|
904
|
+
env_data = {}
|
|
945
905
|
with open(env_path, "r") as f:
|
|
946
906
|
for line in f:
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
host = env_data.get("HOST", "")
|
|
951
|
-
|
|
952
|
-
except FileNotFoundError:
|
|
953
|
-
click.echo("Error: .env with credentials not found")
|
|
954
|
-
return
|
|
955
|
-
except Exception as e:
|
|
956
|
-
click.echo(f"Error reading .env file: {e}")
|
|
957
|
-
return
|
|
958
|
-
|
|
959
|
-
if host.startswith("CMTY_"):
|
|
960
|
-
node_type = questionary.select(
|
|
961
|
-
"Community Cells can only create private Nodes",
|
|
962
|
-
choices=["private"]
|
|
963
|
-
).ask()
|
|
964
|
-
else:
|
|
965
|
-
node_type = questionary.select(
|
|
966
|
-
"Who can view your Node?:",
|
|
967
|
-
choices=["public", "private", "partners"]
|
|
968
|
-
).ask()
|
|
969
|
-
partners = "None"
|
|
970
|
-
if node_type == "partners":
|
|
971
|
-
prompt_msg = (
|
|
972
|
-
"Enter the list of partners who can view this Node.\n"
|
|
973
|
-
"Format: partner::cell, partner::cell, partner::cell\n"
|
|
974
|
-
"Press Enter to leave the list unchanged"
|
|
975
|
-
)
|
|
976
|
-
partners = click.prompt(
|
|
977
|
-
prompt_msg,
|
|
978
|
-
default="None",
|
|
979
|
-
show_default=False
|
|
980
|
-
).strip()
|
|
981
|
-
descr = click.prompt(
|
|
982
|
-
"Update Node description: Type up to 25 characters, or press Enter to leave it unchanged",
|
|
983
|
-
default="None",
|
|
984
|
-
show_default=False
|
|
985
|
-
).strip()
|
|
986
|
-
if descr and len(descr) > 25:
|
|
987
|
-
click.echo("Description too long. Max 25 characters allowed.")
|
|
988
|
-
return
|
|
989
|
-
asyncio.run(async_update_node_at_start(node_type, descr, partners))
|
|
990
|
-
|
|
991
|
-
async def async_update_node_at_start(node_type: str, descr: str, partners:str) -> None:
|
|
992
|
-
credentials_folder_path = Path.home() / ".neuronum"
|
|
993
|
-
env_path = credentials_folder_path / ".env"
|
|
994
|
-
env_data = {}
|
|
907
|
+
if "=" in line:
|
|
908
|
+
key, value = line.strip().split("=", 1)
|
|
909
|
+
env_data[key] = value
|
|
995
910
|
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
for line in f:
|
|
999
|
-
key, value = line.strip().split("=")
|
|
1000
|
-
env_data[key] = value
|
|
911
|
+
with open("config.json", "r") as f:
|
|
912
|
+
config_data = json.load(f)
|
|
1001
913
|
|
|
1002
914
|
host = env_data.get("HOST", "")
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
915
|
+
audience = config_data.get("app_metadata", {}).get("audience", "")
|
|
916
|
+
descr = config_data.get("app_metadata", {}).get("name", "")
|
|
917
|
+
|
|
918
|
+
if host.startswith("CMTY_") and audience != "private":
|
|
919
|
+
raise click.ClickException(
|
|
920
|
+
'Community Cells can only start private Nodes. Node starting "privately".'
|
|
921
|
+
)
|
|
922
|
+
if descr and len(descr) > 25:
|
|
923
|
+
raise click.ClickException(
|
|
924
|
+
'Description too long. Max 25 characters allowed.'
|
|
925
|
+
)
|
|
926
|
+
|
|
927
|
+
asyncio.run(_async_update_node_at_start(env_data, config_data, audience, descr))
|
|
928
|
+
|
|
929
|
+
except FileNotFoundError as e:
|
|
930
|
+
click.echo(f"Error: File not found - {e.filename}")
|
|
931
|
+
except click.ClickException as e:
|
|
932
|
+
click.echo(e.format_message())
|
|
933
|
+
except Exception as e:
|
|
934
|
+
click.echo(f"Unexpected error: {e}")
|
|
1006
935
|
|
|
1007
|
-
with open('config.json', 'r') as f:
|
|
1008
|
-
data = json.load(f)
|
|
1009
936
|
|
|
1010
|
-
|
|
937
|
+
async def _async_update_node_at_start(env_data, config_data, audience, descr):
|
|
938
|
+
host = env_data.get("HOST", "")
|
|
939
|
+
password = env_data.get("PASSWORD", "")
|
|
940
|
+
network = env_data.get("NETWORK", "")
|
|
941
|
+
synapse = env_data.get("SYNAPSE", "")
|
|
1011
942
|
|
|
1012
|
-
|
|
1013
|
-
click.echo("Error: .env with credentials not found")
|
|
1014
|
-
return
|
|
1015
|
-
except Exception as e:
|
|
1016
|
-
click.echo(f"Error reading .env file: {e}")
|
|
1017
|
-
return
|
|
943
|
+
node_id = config_data.get("data_gateways", [{}])[0].get("node_id", "")
|
|
1018
944
|
|
|
1019
945
|
try:
|
|
1020
946
|
with open("config.json", "r") as f:
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
except FileNotFoundError:
|
|
1024
|
-
click.echo("Error: File not found")
|
|
1025
|
-
return
|
|
947
|
+
config_file_content = f.read()
|
|
1026
948
|
except Exception as e:
|
|
1027
|
-
click.echo(f"Error reading
|
|
949
|
+
click.echo(f"Error reading config.json content: {e}")
|
|
1028
950
|
return
|
|
1029
|
-
|
|
1030
|
-
if node_type == "partners":
|
|
1031
|
-
node_type = partners
|
|
1032
951
|
|
|
1033
952
|
url = f"https://{network}/api/update_node"
|
|
1034
953
|
node = {
|
|
1035
|
-
"nodeID":
|
|
954
|
+
"nodeID": node_id,
|
|
1036
955
|
"host": host,
|
|
1037
956
|
"password": password,
|
|
1038
957
|
"synapse": synapse,
|
|
1039
|
-
"node_type":
|
|
1040
|
-
"config_file":
|
|
958
|
+
"node_type": audience,
|
|
959
|
+
"config_file": config_file_content,
|
|
1041
960
|
"descr": descr,
|
|
1042
961
|
}
|
|
1043
962
|
|
|
@@ -1046,15 +965,10 @@ async def async_update_node_at_start(node_type: str, descr: str, partners:str) -
|
|
|
1046
965
|
async with session.post(url, json=node) as response:
|
|
1047
966
|
response.raise_for_status()
|
|
1048
967
|
data = await response.json()
|
|
1049
|
-
|
|
968
|
+
updated_node_id = data.get("nodeID", node_id)
|
|
969
|
+
click.echo(f"Neuronum Node '{updated_node_id}' updated!")
|
|
1050
970
|
except aiohttp.ClientError as e:
|
|
1051
971
|
click.echo(f"Error sending request: {e}")
|
|
1052
|
-
return
|
|
1053
|
-
|
|
1054
|
-
if node_type == "public":
|
|
1055
|
-
click.echo(f"Neuronum Node '{nodeID}' updated!")
|
|
1056
|
-
else:
|
|
1057
|
-
click.echo(f"Neuronum Node '{nodeID}' updated!")
|
|
1058
972
|
|
|
1059
973
|
|
|
1060
974
|
@click.command()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .neuronum import Node
|
|
@@ -14,10 +14,11 @@ from cryptography.hazmat.primitives.kdf.hkdf import HKDF
|
|
|
14
14
|
from cryptography.hazmat.backends import default_backend
|
|
15
15
|
|
|
16
16
|
|
|
17
|
-
class
|
|
18
|
-
def __init__(self,
|
|
19
|
-
self.
|
|
20
|
-
self.
|
|
17
|
+
class Node:
|
|
18
|
+
def __init__(self, id: str, private_key: str, public_key: str):
|
|
19
|
+
self.node_id = id
|
|
20
|
+
self.private_key_path = private_key
|
|
21
|
+
self.public_key_path = public_key
|
|
21
22
|
self.queue = asyncio.Queue()
|
|
22
23
|
self.host = self._load_host()
|
|
23
24
|
self.network = self._load_network()
|
|
@@ -34,6 +35,7 @@ class Cell:
|
|
|
34
35
|
"synapse": self.synapse
|
|
35
36
|
}
|
|
36
37
|
|
|
38
|
+
|
|
37
39
|
def _load_private_key(self):
|
|
38
40
|
try:
|
|
39
41
|
with open(self.private_key_path, "rb") as f:
|
|
@@ -42,12 +44,12 @@ class Cell:
|
|
|
42
44
|
password=None,
|
|
43
45
|
backend=default_backend()
|
|
44
46
|
)
|
|
45
|
-
print("Private key loaded successfully.")
|
|
46
47
|
return private_key
|
|
47
48
|
except FileNotFoundError:
|
|
48
49
|
print(f"Private key file not found at {self.private_key_path}.")
|
|
49
50
|
return None
|
|
50
51
|
|
|
52
|
+
|
|
51
53
|
def _load_host(self):
|
|
52
54
|
credentials_folder_path = Path.home() / ".neuronum"
|
|
53
55
|
env_path = credentials_folder_path / ".env"
|
|
@@ -129,8 +131,6 @@ class Cell:
|
|
|
129
131
|
f.read(),
|
|
130
132
|
backend=default_backend()
|
|
131
133
|
)
|
|
132
|
-
print("Public key loaded successfully.")
|
|
133
|
-
print(public_key)
|
|
134
134
|
return public_key
|
|
135
135
|
except FileNotFoundError:
|
|
136
136
|
print(f"Public key file not found at {self.public_key_path}. Deriving from private key.")
|
|
@@ -146,7 +146,6 @@ class Cell:
|
|
|
146
146
|
print("Public key not loaded. Cannot generate JWK.")
|
|
147
147
|
return None
|
|
148
148
|
|
|
149
|
-
print("Public key loaded successfully.")
|
|
150
149
|
public_numbers = public_key.public_numbers()
|
|
151
150
|
|
|
152
151
|
x_bytes = public_numbers.x.to_bytes((public_numbers.x.bit_length() + 7) // 8, 'big')
|
|
@@ -184,8 +183,8 @@ class Cell:
|
|
|
184
183
|
return None
|
|
185
184
|
|
|
186
185
|
|
|
187
|
-
async def sync(self
|
|
188
|
-
full_url = f"wss://{self.network}/sync/{node_id}"
|
|
186
|
+
async def sync(self) -> AsyncGenerator[str, None]:
|
|
187
|
+
full_url = f"wss://{self.network}/sync/{self.node_id}"
|
|
189
188
|
auth_payload = {
|
|
190
189
|
"host": self.host,
|
|
191
190
|
"password": self.password,
|
|
@@ -195,7 +194,7 @@ class Cell:
|
|
|
195
194
|
try:
|
|
196
195
|
async with websockets.connect(full_url) as ws:
|
|
197
196
|
await ws.send(json.dumps(auth_payload))
|
|
198
|
-
print("
|
|
197
|
+
print("Node syncing...")
|
|
199
198
|
while True:
|
|
200
199
|
try:
|
|
201
200
|
raw_operation = await ws.recv()
|
|
@@ -261,108 +260,7 @@ class Cell:
|
|
|
261
260
|
print(f"Error sending request: {e}")
|
|
262
261
|
except Exception as e:
|
|
263
262
|
print(f"Unexpected error: {e}")
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
async def activate_tx(self, node_id: str, data: dict):
|
|
267
|
-
url = f"https://{self.network}/api/activate_tx/{node_id}"
|
|
268
|
-
|
|
269
|
-
nodes = await self.list_nodes()
|
|
270
|
-
|
|
271
|
-
target_public_key_pem = None
|
|
272
|
-
target_node = node_id
|
|
273
|
-
|
|
274
|
-
for node in nodes:
|
|
275
|
-
node_ids = node.get('config', {}).get('data_gateways', [])
|
|
276
|
-
for node_id_entry in node_ids:
|
|
277
|
-
if node_id_entry.get('node_id') == target_node:
|
|
278
|
-
target_public_key_pem = node.get('config', {}).get('public_key')
|
|
279
|
-
break
|
|
280
|
-
if target_public_key_pem:
|
|
281
|
-
break
|
|
282
|
-
|
|
283
|
-
if not target_public_key_pem:
|
|
284
|
-
print(f"Target node not found or public key is missing.")
|
|
285
|
-
return None
|
|
286
|
-
|
|
287
|
-
try:
|
|
288
|
-
print(target_public_key_pem)
|
|
289
|
-
partner_public_key_jwk = self._load_public_key_from_pem(target_public_key_pem)
|
|
290
|
-
if not partner_public_key_jwk:
|
|
291
|
-
print("Failed to convert public key to JWK format. Aborting.")
|
|
292
|
-
return None
|
|
293
|
-
|
|
294
|
-
partner_public_key = self._load_public_key_from_jwk(partner_public_key_jwk)
|
|
295
|
-
if not partner_public_key:
|
|
296
|
-
print("Failed to load partner's public key. Aborting.")
|
|
297
|
-
return None
|
|
298
|
-
|
|
299
|
-
data_to_encrypt = data.copy()
|
|
300
|
-
data_to_encrypt["publicKey"] = self.get_public_key_jwk()
|
|
301
|
-
|
|
302
|
-
encrypted_payload = self._encrypt_with_ecdh_aesgcm(partner_public_key, data_to_encrypt)
|
|
303
|
-
|
|
304
|
-
TX = {
|
|
305
|
-
"data": {
|
|
306
|
-
"encrypted": encrypted_payload
|
|
307
|
-
},
|
|
308
|
-
"cell": self.to_dict()
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
async with aiohttp.ClientSession() as session:
|
|
312
|
-
async with session.post(url, json=TX) as response:
|
|
313
|
-
if response.status == 504:
|
|
314
|
-
print(f"Gateway Timeout: No response from transmitter for txID: {node_id}")
|
|
315
|
-
return None
|
|
316
|
-
|
|
317
|
-
response.raise_for_status()
|
|
318
|
-
|
|
319
|
-
response_data = await response.json()
|
|
320
|
-
|
|
321
|
-
if "response" in response_data:
|
|
322
|
-
inner_response = response_data["response"]
|
|
323
|
-
|
|
324
|
-
if "ciphertext" in inner_response:
|
|
325
|
-
ephemeral_public_key_b64 = inner_response["ephemeralPublicKey"]
|
|
326
|
-
ephemeral_public_key_b64 += '=' * ((4 - len(ephemeral_public_key_b64) % 4) % 4)
|
|
327
|
-
ephemeral_public_key_bytes = base64.urlsafe_b64decode(ephemeral_public_key_b64)
|
|
328
|
-
|
|
329
|
-
nonce_b64 = inner_response["nonce"]
|
|
330
|
-
nonce_b64 += '=' * ((4 - len(nonce_b64) % 4) % 4)
|
|
331
|
-
nonce = base64.urlsafe_b64decode(nonce_b64)
|
|
332
|
-
|
|
333
|
-
ciphertext_b64 = inner_response["ciphertext"]
|
|
334
|
-
ciphertext_b64 += '=' * ((4 - len(ciphertext_b64) % 4) % 4)
|
|
335
|
-
ciphertext = base64.urlsafe_b64decode(ciphertext_b64)
|
|
336
|
-
|
|
337
|
-
decrypted_response = self._decrypt_with_ecdh_aesgcm(
|
|
338
|
-
ephemeral_public_key_bytes, nonce, ciphertext
|
|
339
|
-
)
|
|
340
|
-
|
|
341
|
-
if decrypted_response:
|
|
342
|
-
return decrypted_response
|
|
343
|
-
else:
|
|
344
|
-
print("Failed to decrypt server response.")
|
|
345
|
-
return None
|
|
346
|
-
else:
|
|
347
|
-
print("Server response was not encrypted as expected.")
|
|
348
|
-
return inner_response
|
|
349
|
-
|
|
350
|
-
else:
|
|
351
|
-
print("Unexpected response format.")
|
|
352
|
-
return response_data
|
|
353
|
-
|
|
354
|
-
except aiohttp.ClientResponseError as e:
|
|
355
|
-
print(f"HTTP Error: {e.status}, Message: {e.message}, URL: {e.request_info.url}")
|
|
356
|
-
return None
|
|
357
|
-
|
|
358
|
-
except aiohttp.ClientError as e:
|
|
359
|
-
print(f"Connection Error: {e}")
|
|
360
|
-
return None
|
|
361
|
-
|
|
362
|
-
except Exception as e:
|
|
363
|
-
print(f"Unexpected error: {e}")
|
|
364
|
-
return None
|
|
365
|
-
|
|
263
|
+
|
|
366
264
|
|
|
367
265
|
def _load_public_key_from_jwk(self, jwk):
|
|
368
266
|
try:
|
|
@@ -378,31 +276,6 @@ class Cell:
|
|
|
378
276
|
except (KeyError, ValueError, TypeError) as e:
|
|
379
277
|
print(f"Error loading public key from JWK string: {e}")
|
|
380
278
|
return None
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
def _load_public_key_from_pem(self, pem_string: str):
|
|
384
|
-
try:
|
|
385
|
-
print(pem_string)
|
|
386
|
-
corrected_pem_string = pem_string.replace("-----BEGINPUBLICKEY-----", "-----BEGIN PUBLIC KEY-----")
|
|
387
|
-
corrected_pem_string = corrected_pem_string.replace("-----ENDPUBLICKEY-----", "-----END PUBLIC KEY-----")
|
|
388
|
-
|
|
389
|
-
public_key = serialization.load_pem_public_key(
|
|
390
|
-
corrected_pem_string.encode(),
|
|
391
|
-
backend=default_backend()
|
|
392
|
-
)
|
|
393
|
-
|
|
394
|
-
public_numbers = public_key.public_numbers()
|
|
395
|
-
x_bytes = public_numbers.x.to_bytes((public_numbers.x.bit_length() + 7) // 8, 'big')
|
|
396
|
-
y_bytes = public_numbers.y.to_bytes((public_numbers.y.bit_length() + 7) // 8, 'big')
|
|
397
|
-
return {
|
|
398
|
-
"kty": "EC",
|
|
399
|
-
"crv": "P-256",
|
|
400
|
-
"x": base64.urlsafe_b64encode(x_bytes).rstrip(b'=').decode('utf-8'),
|
|
401
|
-
"y": base64.urlsafe_b64encode(y_bytes).rstrip(b'=').decode('utf-8')
|
|
402
|
-
}
|
|
403
|
-
except Exception as e:
|
|
404
|
-
print(f"Error loading public key from PEM string: {e}")
|
|
405
|
-
return None
|
|
406
279
|
|
|
407
280
|
|
|
408
281
|
def _encrypt_with_ecdh_aesgcm(self, public_key, plaintext_dict):
|
|
@@ -462,6 +335,4 @@ class Cell:
|
|
|
462
335
|
except aiohttp.ClientError as e:
|
|
463
336
|
print(f"Error sending request: {e}")
|
|
464
337
|
except Exception as e:
|
|
465
|
-
print(f"Unexpected error: {e}")
|
|
466
|
-
|
|
467
|
-
__all__ = ['Cell']
|
|
338
|
+
print(f"Unexpected error: {e}")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: neuronum
|
|
3
|
-
Version: 8.
|
|
3
|
+
Version: 8.2.0
|
|
4
4
|
Summary: The E2E Web Engine
|
|
5
5
|
Home-page: https://neuronum.net
|
|
6
6
|
Author: Neuronum Cybernetics
|
|
@@ -71,7 +71,7 @@ Neuronum is a real-time web engine designed for developers to build E2E-native a
|
|
|
71
71
|
### **Tools & Features**
|
|
72
72
|
- Cell: Account to interact with Neuronum
|
|
73
73
|
- Nodes: Apps & Services built on Neuronum
|
|
74
|
-
- Browser:
|
|
74
|
+
- Browser: Web Browser built on Neuronum -> [build from source](https://github.com/neuronumcybernetics/neuronum_browser)
|
|
75
75
|
|
|
76
76
|
### Requirements
|
|
77
77
|
- Python >= 3.8
|
|
@@ -100,8 +100,6 @@ neuronum connect-cell
|
|
|
100
100
|
|
|
101
101
|
|
|
102
102
|
### **Build On Neuronum**
|
|
103
|
-
Visit & build with [Node Examples](https://github.com/neuronumcybernetics/neuronum/tree/main/features/nodes/examples) to gain deeper knowledge on how to build on Neuronum.
|
|
104
|
-
|
|
105
103
|
To get started, initialize a new Node with the command below.
|
|
106
104
|
```sh
|
|
107
105
|
neuronum init-node
|
|
@@ -123,4 +121,4 @@ neuronum start-node
|
|
|
123
121
|
|
|
124
122
|
### **Interact with your Node**
|
|
125
123
|
|
|
126
|
-
The **Neuronum Browser** is an open source web
|
|
124
|
+
The **Neuronum Browser** is an open source E2E web browser that allows you to interact with your nodes -> [build from source](https://github.com/neuronumcybernetics/neuronum_browser)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|