Added libs
This commit is contained in:
1
p2private/.mtimes.json
Normal file
1
p2private/.mtimes.json
Normal file
@@ -0,0 +1 @@
|
||||
{"config.json": 1756118682.0519147, "dataManager.py": 1757749170.2625794, "networks.py": 1757800810.4118917, "networksApiBlueprint.py": 1757750515.750099, "p2private.py": 1757803028.377009, "p2privateApiBlueprint.py": 1757825501.4272363, "vue\\router.js": 1755335641.4535544, "vue\\api\\p2privateApi.js": 1757756279.5687172, "vue\\api\\socketEvents.js": 1757748445.9564493, "vue\\views\\HomeView.vue": 1757756292.8398924}
|
||||
1
p2private/config.json
Normal file
1
p2private/config.json
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
47
p2private/dataManager.py
Normal file
47
p2private/dataManager.py
Normal file
@@ -0,0 +1,47 @@
|
||||
from typing import Dict, List, Optional, Any
|
||||
from libs.app.common.store import DataStore
|
||||
from libs.fspn.utils.sha256_util import hash_string
|
||||
|
||||
class DataManager():
|
||||
def __init__(self):
|
||||
self.store = DataStore(path="p2private/data.json", default_data={"friends":[], "messages":[]})
|
||||
|
||||
# ------------------- FRIEND CRUD -------------------
|
||||
def add_friend(self, pubkey: str, relays):
|
||||
if self.get_friend(pubkey):
|
||||
raise ValueError(f"Friend {pubkey} already exists")
|
||||
friend = {"pubkey": pubkey, "relays": relays}
|
||||
self.store.add_item("friends", friend, unique=True, id_field="pubkey", id=pubkey)
|
||||
return friend
|
||||
|
||||
def get_friend(self, pubkey: str):
|
||||
return self.store.get_item("friends", "pubkey", pubkey)
|
||||
|
||||
def delete_friend(self, pubkey: str) -> bool:
|
||||
return self.store.remove_item("friends", "pubkey", pubkey)
|
||||
|
||||
def list_friends(self):
|
||||
return self.store.list_items("friends")
|
||||
|
||||
def update_friend(self, pubkey: str, updates: Dict[str, Any]) -> bool:
|
||||
return self.store.update_item("friends", "pubkey", pubkey, updates)
|
||||
|
||||
# ------------------- MESSAGE CRUD -------------------
|
||||
def add_message(self, message):
|
||||
if self.get_message(message['hash']):
|
||||
raise ValueError(f"Message {message['hash']} already exists")
|
||||
self.store.add_item("messages", message, unique=True, id_field="hash", id=message["hash"])
|
||||
return message
|
||||
|
||||
def get_message(self, hash: str):
|
||||
return self.store.get_item("messages", "hash", hash)
|
||||
|
||||
def delete_message(self, hash: str) -> bool:
|
||||
return self.store.remove_item("messages", "hash", hash)
|
||||
|
||||
def list_messages(self):
|
||||
return self.store.list_items("messages")
|
||||
|
||||
def update_message(self, hash: str, updates: Dict[str, Any]) -> bool:
|
||||
return self.store.update_item("messages", "hash", hash, updates)
|
||||
|
||||
15
p2private/info.json
Normal file
15
p2private/info.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"id": "p2private",
|
||||
"version": 0.043,
|
||||
"modules": [
|
||||
{
|
||||
"id": "p2private",
|
||||
"version": 0
|
||||
},
|
||||
{
|
||||
"id": "networks",
|
||||
"version": 0
|
||||
}
|
||||
],
|
||||
"frontend": "vue"
|
||||
}
|
||||
158
p2private/networks.py
Normal file
158
p2private/networks.py
Normal file
@@ -0,0 +1,158 @@
|
||||
from typing import Dict, List, Optional, Any
|
||||
from libs.fspn.utils.sha256_util import hash_string
|
||||
from libs.app.common.store import DataStore
|
||||
from libs.app.common.logging import get_logger
|
||||
from libs.noSys.noSysModule import NoSysModule
|
||||
from libs.noSys.peers import Peer
|
||||
from .networksApiBlueprint import Blueprint
|
||||
|
||||
logger = get_logger("networks")
|
||||
|
||||
class Networks(NoSysModule):
|
||||
def __init__(self, nosys_core):
|
||||
super().__init__(nosys_core)
|
||||
|
||||
self.store = DataStore(path="p2private/networkData.json", default_data={"networks":[], "messages":[]})
|
||||
self.network_states = {}
|
||||
|
||||
def setup(self):
|
||||
self.nosys_core.modules.api.register_blueprint(Blueprint(self).blueprint)
|
||||
from .p2private import P2private
|
||||
self.p2private_module:P2private = self.nosys_core.modules.get("p2private", "p2private")
|
||||
from libs.p2post.p2post import P2post
|
||||
self.p2post_module:P2post = self.nosys_core.modules.get("p2post", "p2post")
|
||||
|
||||
def on_nosys_ready(self, event):
|
||||
for network in self.networks:
|
||||
if not self.get_network(network["id"]):
|
||||
self.add_network(network["id"])
|
||||
|
||||
def on_module_message(self, event):
|
||||
handler_action = getattr(self, 'on_'+event.data.get("action"))
|
||||
handler_action(event)
|
||||
|
||||
def post_network_message(self, message, networks=[]):
|
||||
for network_id in networks:
|
||||
if not self.get_message(message["hash"]):
|
||||
self.add_message(message["hash"], message["from"], message["to"], message["content"], message["timestamp"], message["signature"])
|
||||
self.add_message_to_network(network_id, message["hash"])
|
||||
|
||||
state = self.network_states.get(network_id, [])
|
||||
for peer in state.get("peers", []):
|
||||
self.send_network_message(network_id, message["hash"], peer)
|
||||
|
||||
def on_network_connection(self, event):
|
||||
network_id = event.network_id
|
||||
peer_id = event.peer.id
|
||||
if network_id not in self.network_states:
|
||||
self.network_states[network_id] = {"peers":[], "status":0} # TODO ENUM status
|
||||
self.network_states[network_id]["peers"].append(peer_id)
|
||||
self.send_network_hash(network_id, peer_id)
|
||||
|
||||
def send_network_hash(self, network_id:str, peer_id:str):
|
||||
hash = self.get_network_hash(network_id)
|
||||
self.nosys_core.dispatcher.send_message({'action':'network_hash','network':network_id, "hash":hash}, peer_id, self.id)
|
||||
|
||||
def on_network_hash(self, event):
|
||||
peer: Peer = event.peer
|
||||
data = event.data
|
||||
network_id = data["network"]
|
||||
if data["hash"] != self.get_network_hash(network_id):
|
||||
self.send_network_messages(network_id, peer.id)
|
||||
|
||||
def send_network_messages(self, network_id, peer_id):
|
||||
messages = self.get_network(network_id)["messages"]
|
||||
self.nosys_core.dispatcher.send_message({'action':'network_messages','network':network_id, "messages":messages}, peer_id, self.id)
|
||||
|
||||
def on_network_messages(self, event):
|
||||
peer: Peer = event.peer
|
||||
data = event.data
|
||||
network_id = data["network"]
|
||||
messages = data["messages"]
|
||||
for hash in messages:
|
||||
if not self.get_message(hash):
|
||||
self.send_get_network_message(network_id, hash, peer.id)
|
||||
|
||||
def send_get_network_message(self, network_id, message_id, peer_id):
|
||||
self.nosys_core.dispatcher.send_message({'action':'get_network_message', "network":network_id, "message":message_id}, peer_id, self.id)
|
||||
|
||||
def on_get_network_message(self, event):
|
||||
peer: Peer = event.peer
|
||||
data = event.data
|
||||
self.send_network_message(data["network"], data["message"], peer.id)
|
||||
|
||||
def send_network_message(self, network_id, message_id, peer_id):
|
||||
message = self.get_message(message_id)
|
||||
self.nosys_core.dispatcher.send_message({'action':'network_message', "network":network_id, "message":message}, peer_id, self.id)
|
||||
|
||||
def on_network_message(self, event):
|
||||
data = event.data
|
||||
network_id = data["network"]
|
||||
message = data["message"]
|
||||
if not self.get_message(message["hash"]):
|
||||
self.add_message(message["hash"], message["from"], message["to"], message["content"], message["timestamp"], message["signature"])
|
||||
# TODO Check if not in network messages
|
||||
self.add_message_to_network(network_id, message["hash"])
|
||||
self.post_network_message(message, [network_id])
|
||||
self.check_message_to_me(message)
|
||||
|
||||
def check_message_to_me(self, message):
|
||||
if self.p2post_module.data.get_user(message["to"]):
|
||||
self.p2private_module.data.add_message(message)
|
||||
# TODO SEND RECEIVED SIGNATURE TO THE NETWORKS IF USER LOGGED
|
||||
|
||||
def get_network_hash(self, network_id: str) -> str:
|
||||
network = self.get_network(network_id)
|
||||
if not network:
|
||||
return ""
|
||||
combined = ''.join(sorted(network["messages"]))
|
||||
return hash_string(combined)
|
||||
|
||||
# ------------------- MESSAGE CRUD -------------------
|
||||
def add_message(self, hash: str, from_id:str, to_id:str, content:str, timestamp, signature:str):
|
||||
if self.get_message(hash):
|
||||
raise ValueError(f"Message {hash} already exists")
|
||||
message = {"hash": hash, "from": from_id, "to":to_id, "content":content, "timestamp":timestamp, "signature":signature}
|
||||
self.store.add_item("messages", message, unique=True, id_field="hash", id=hash)
|
||||
return message
|
||||
|
||||
def get_message(self, hash: str):
|
||||
return self.store.get_item("messages", "hash", hash)
|
||||
|
||||
def delete_message(self, hash: str) -> bool:
|
||||
return self.store.remove_item("messages", "hash", hash)
|
||||
|
||||
def list_messages(self):
|
||||
return self.store.list_items("messages")
|
||||
|
||||
def update_message(self, hash: str, updates: Dict[str, Any]) -> bool:
|
||||
return self.store.update_item("messages", "hash", hash, updates)
|
||||
|
||||
# ------------------- NETWORK CRUD -------------------
|
||||
def add_network(self, network_id: str):
|
||||
if self.get_network(network_id):
|
||||
raise ValueError(f"Network {network_id} already exists")
|
||||
network = {"id": network_id, "messages": []}
|
||||
self.store.add_item("networks", network, unique=True, id_field="id", id=network_id)
|
||||
return network
|
||||
|
||||
def get_network(self, network_id: str):
|
||||
return self.store.get_item("networks", "id", network_id)
|
||||
|
||||
def delete_network(self, network_id: str) -> bool:
|
||||
return self.store.remove_item("networks", "id", network_id)
|
||||
|
||||
def list_networks(self):
|
||||
return self.store.list_items("networks")
|
||||
|
||||
def update_network(self, network_id: str, updates: Dict[str, Any]) -> bool:
|
||||
return self.store.update_item("networks", "id", network_id, updates)
|
||||
|
||||
def add_message_to_network(self, network_id:str, message_id:str):
|
||||
network = self.get_network(network_id)
|
||||
if network and message_id not in network["messages"]:
|
||||
network["messages"].append(message_id)
|
||||
self.update_network(network_id, network)
|
||||
return True
|
||||
return False
|
||||
|
||||
23
p2private/networksApiBlueprint.py
Normal file
23
p2private/networksApiBlueprint.py
Normal file
@@ -0,0 +1,23 @@
|
||||
from libs.api.apiBlueprint import ApiBlueprint
|
||||
from libs.app.common.paths import ROOT_DIR
|
||||
|
||||
from flask import jsonify, request, send_from_directory
|
||||
|
||||
import os
|
||||
import copy
|
||||
import random
|
||||
|
||||
class Blueprint(ApiBlueprint):
|
||||
def routes(self):
|
||||
from .networks import Networks
|
||||
self.module:Networks = self.module
|
||||
|
||||
@self.blueprint.route('/')
|
||||
def show():
|
||||
return self.module.name
|
||||
|
||||
@self.blueprint.route('/networks', methods=["GET"])
|
||||
def networks():
|
||||
if request.method == "GET":
|
||||
return jsonify(self.module.networks)
|
||||
|
||||
161
p2private/p2private.py
Normal file
161
p2private/p2private.py
Normal file
@@ -0,0 +1,161 @@
|
||||
import os
|
||||
from datetime import datetime, timezone
|
||||
import json
|
||||
import time
|
||||
|
||||
from libs.fspn.utils.wrapper_util import threaded
|
||||
from libs.app.common.logging import get_logger
|
||||
from libs.fspn.utils.sha256_util import hash_file, hash_bytes, hash_string
|
||||
from libs.noSys.noSysModule import NoSysModule
|
||||
from libs.noSys.events import Events as nosys_events, DynamicEvents as nosys_dynamic_events
|
||||
from libs.noSys.peers import Peer
|
||||
from libs.p2post.p2post import P2post
|
||||
from .networks import Networks
|
||||
from .dataManager import DataManager
|
||||
from .p2privateApiBlueprint import Blueprint
|
||||
|
||||
logger = get_logger()
|
||||
|
||||
class P2private(NoSysModule):
|
||||
def __init__(self, noSys):
|
||||
super().__init__(noSys)
|
||||
self.nosys_core.subscribe_event(nosys_events.USER_ADDED, self.on_user_added)
|
||||
|
||||
self.data = DataManager()
|
||||
self.p2post:P2post = None
|
||||
self.networks_module:Networks = None
|
||||
self.requested_signatures = {}
|
||||
|
||||
self.friends_state = {}
|
||||
|
||||
def setup(self):
|
||||
self.p2post = self.nosys_core.modules.get("p2post", "p2post")
|
||||
self.networks_module = self.nosys_core.modules.get("p2private", "networks")
|
||||
self.nosys_core.modules.api.register_blueprint(Blueprint(self).blueprint)
|
||||
|
||||
def on_nosys_ready(self, event):
|
||||
pass
|
||||
|
||||
def add_friend(self, pubkey, relays):
|
||||
self.data.add_friend(pubkey, relays)
|
||||
self.set_friend_state(pubkey)
|
||||
|
||||
def on_user_added(self, event):
|
||||
user_id:str = event.user_id
|
||||
self.manage_friends(user_id)
|
||||
|
||||
@threaded
|
||||
def manage_friends(self, my_user):
|
||||
for friend in self.data.list_friends():
|
||||
self.set_friend_state(friend["pubkey"])
|
||||
while True:
|
||||
for friend in self.data.list_friends():
|
||||
self.manage_friend(friend)
|
||||
time.sleep(10)
|
||||
|
||||
def set_friend_state(self, friend_pubkey):
|
||||
# TODO Add friends inside users object data
|
||||
self.friends_state[friend_pubkey] = {
|
||||
"id": friend_pubkey,
|
||||
"status": "dis", # TODO Enum
|
||||
}
|
||||
|
||||
def manage_friend(self, user):
|
||||
friend_id = user["pubkey"]
|
||||
state = self.friends_state[friend_id]
|
||||
|
||||
if state["status"] == "dis":
|
||||
print("FRIEND IS DISCONNECTED")
|
||||
for relay in user["relays"]:
|
||||
if self.networks_module.network_states.get(relay):
|
||||
print(f"Connected to relay {relay}")
|
||||
# TODO Check if friend is on in the network and send message to connect to a rendezvous
|
||||
elif state["status"] == "con":
|
||||
print("FRIEND IS CONNECTED")
|
||||
|
||||
def on_module_message(self, event):
|
||||
handler_action = getattr(self, 'on_'+event.data.get("action"))
|
||||
handler_action(event)
|
||||
|
||||
def create_message(self, from_user, to_user, content, medias):
|
||||
medias_data = []
|
||||
for media in medias:
|
||||
if media["type"] == "local":
|
||||
file_hash = os.path.splitext(os.path.basename(media["file_path"]))[0]
|
||||
medias_data.append({"type":"local", "hash":file_hash})
|
||||
self.p2post.data.add_media(file_hash, media["file_path"])
|
||||
else:
|
||||
medias_data.append({"type":media["type"], "url":media["url"]})
|
||||
|
||||
current_utc_datetime = datetime.now(timezone.utc)
|
||||
utc_timestamp = current_utc_datetime.timestamp()
|
||||
message = {
|
||||
"timestamp": utc_timestamp,
|
||||
"from": from_user,
|
||||
"to": to_user,
|
||||
"content": content, #TODO Hash it
|
||||
"medias": medias_data
|
||||
}
|
||||
message_serialized = json.dumps(message, sort_keys=True, separators=(",", ":"))
|
||||
|
||||
message["hash"] = hash_bytes(message_serialized.encode('utf-8'))
|
||||
request_id = self.nosys_core.modules.pmc.sign(message["hash"], from_user, self.signature_callback, f"New Message {message}")
|
||||
self.requested_signatures[request_id] = message
|
||||
logger.debug(f"Message waiting signature {request_id}: {message}")
|
||||
return message["hash"]
|
||||
|
||||
def signature_callback(self, request_id, signature):
|
||||
message = self.requested_signatures.get(request_id)
|
||||
if signature and message:
|
||||
message["signature"] = signature
|
||||
self.send_message(message)
|
||||
|
||||
def send_message(self, message):
|
||||
self.data.add_message(message)
|
||||
direct_connections = self.nosys_core.peers.get_by_peer_user_id(message["to"])
|
||||
if direct_connections:
|
||||
logger.debug(f"Friend {message['to']} direct connected")
|
||||
for peer in direct_connections:
|
||||
# TODO Maybe check 'from user', peers can set to just receive message from the user in connection and networks
|
||||
self.send_private_message(message, peer.id)
|
||||
else:
|
||||
logger.debug(f"Friend {message['to']} not direct connected. Posting message to user relays.")
|
||||
to_user = message["to"]
|
||||
relay_networks = self.get_friend_relay_networks(to_user) # TODO get from store
|
||||
if not relay_networks:
|
||||
logger.error(f"Not found a network relay to friend {to_user}")
|
||||
# TODO Update message status
|
||||
else:
|
||||
self.networks_module.post_network_message(message, relay_networks)
|
||||
|
||||
def send_private_message(self, message, peer_id):
|
||||
payload = {'action':'private_message','message':message}
|
||||
self.nosys_core.dispatcher.send_message(payload, peer_id, self.id)
|
||||
|
||||
def on_private_message(self, event):
|
||||
data = event.data
|
||||
message = data["message"]
|
||||
if not self.data.get_message(message["hash"]):
|
||||
self.data.add_message(message)
|
||||
logger.debug(f"New message received {message}")
|
||||
else:
|
||||
logger.debug("Message already exists")
|
||||
|
||||
# Read receipts ???? Talk to friends about it
|
||||
|
||||
def get_friend_relay_networks(self, user_id):
|
||||
user = self.data.get_friend(user_id)
|
||||
if user:
|
||||
return user["relays"]
|
||||
return []
|
||||
|
||||
def on_module_connection(self, event):
|
||||
peer:Peer = event.peer
|
||||
peer_user = peer.connection.security.peer_user
|
||||
state = self.friends_state.get(peer_user)
|
||||
if state:
|
||||
state["status"] = "con"
|
||||
|
||||
|
||||
|
||||
|
||||
BIN
p2private/p2private.zip
Normal file
BIN
p2private/p2private.zip
Normal file
Binary file not shown.
85
p2private/p2privateApiBlueprint.py
Normal file
85
p2private/p2privateApiBlueprint.py
Normal file
@@ -0,0 +1,85 @@
|
||||
from libs.api.apiBlueprint import ApiBlueprint
|
||||
from libs.app.common.paths import ROOT_DIR
|
||||
|
||||
from flask import jsonify, request, send_from_directory
|
||||
|
||||
import os
|
||||
import copy
|
||||
import random
|
||||
|
||||
MEDIAS_FOLDER = os.path.join(ROOT_DIR, "files")
|
||||
|
||||
class Blueprint(ApiBlueprint):
|
||||
def routes(self):
|
||||
from .p2private import P2private
|
||||
self.module:P2private = self.module
|
||||
|
||||
@self.blueprint.route('/')
|
||||
def show():
|
||||
return self.module.name
|
||||
|
||||
@self.blueprint.route('/friends', methods=["GET", "POST", "DELETE"])
|
||||
def friends():
|
||||
if request.method == "GET":
|
||||
return jsonify(self.module.data.list_friends())
|
||||
elif request.method == "POST":
|
||||
content:dict = request.json
|
||||
self.module.add_friend(content["pubkey"], content["relays"])
|
||||
return jsonify()
|
||||
elif request.method == "DELETE":
|
||||
pass
|
||||
|
||||
@self.blueprint.route('/messages/<path:user_id>/<path:friend_id>', methods=["GET", "POST"])
|
||||
def messages():
|
||||
if request.method == "GET":
|
||||
return jsonify()
|
||||
elif request.method == "POST":
|
||||
content:dict = request.json
|
||||
message = self.module.create_message(content["from"], content["to"], content["content"], content["medias"])
|
||||
return jsonify(message)
|
||||
|
||||
# @self.blueprint.route('/posts', methods=["GET", "POST"])
|
||||
# def create_post():
|
||||
# if request.method == "GET":
|
||||
# raw_posts = self.module.data_store.data["posts"]
|
||||
# posts = copy.deepcopy(raw_posts)
|
||||
# for post in posts:
|
||||
# for media in post.get("medias"):
|
||||
# if media.get("type") == "local":
|
||||
# media_info = self.module.data_store.get_media(media.get("hash"))
|
||||
# media["file_path"] = media_info.get("file_path")
|
||||
# return jsonify(posts)
|
||||
|
||||
# elif request.method == "POST":
|
||||
# content:dict = request.json
|
||||
# hash = self.module.create_post(content["user"], content["content"], content["medias"], content["networks"])
|
||||
# return jsonify(hash)
|
||||
|
||||
# @self.blueprint.route('/medias', methods=['POST'])
|
||||
# def upload_file():
|
||||
# file = request.files.get("file")
|
||||
# if not file:
|
||||
# return jsonify({"error": "Empty file"}), 400
|
||||
|
||||
# file_bytes = file.read()
|
||||
# file_hash = hash_bytes(file_bytes)
|
||||
# file_ext = os.path.splitext(file.filename)[1]
|
||||
# file_path = os.path.join(MEDIAS_FOLDER, f"{file_hash}{file_ext}")
|
||||
|
||||
# if not os.path.exists(file_path):
|
||||
# with open(file_path, "wb") as f:
|
||||
# f.write(file_bytes)
|
||||
|
||||
# return jsonify({
|
||||
# "status": "ok",
|
||||
# "hash": file_hash,
|
||||
# "path": f"{file_hash}{file_ext}"
|
||||
# })
|
||||
|
||||
# @self.blueprint.route('/medias/<filename>')
|
||||
# def media_file(filename):
|
||||
# return send_from_directory(MEDIAS_FOLDER, filename)
|
||||
|
||||
|
||||
|
||||
|
||||
109
p2private/vue/api/p2privateApi.js
Normal file
109
p2private/vue/api/p2privateApi.js
Normal file
@@ -0,0 +1,109 @@
|
||||
|
||||
|
||||
const p2privateApiUrl = "/api/p2private"
|
||||
|
||||
export const p2privateApi = {
|
||||
async getNetworks(){
|
||||
try {
|
||||
const response = await fetch(p2privateApiUrl+'/networks')
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
throw new Error(`Error fetching networks: ${response.status} - ${errorText}`);
|
||||
}
|
||||
const result = await response.json()
|
||||
// p2postStore.users = result
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error("Error fetching networks:", error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
async getFriends(){
|
||||
try {
|
||||
const response = await fetch(p2privateApiUrl+'/friends')
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
throw new Error(`Error fetching networks: ${response.status} - ${errorText}`);
|
||||
}
|
||||
const result = await response.json()
|
||||
// p2postStore.users = result
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error("Error fetching networks:", error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
async createMessage(from, to, content, medias){
|
||||
const data = {"from":from, "to":to, "content":content, "medias":medias}
|
||||
const requestOptions = {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json',},
|
||||
body: JSON.stringify(data),
|
||||
};
|
||||
try {
|
||||
const response = await fetch(p2privateApiUrl+"/messages", requestOptions);
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
throw new Error(`Error fetching posts: ${response.status} - ${errorText}`);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
return result;
|
||||
|
||||
} catch (error) {
|
||||
console.error("Error fetching posts:", error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// async createPosts(user, content, medias, networks){
|
||||
// const data = {"user": user, "content": content, "medias": medias, "networks": networks};
|
||||
// const requestOptions = {
|
||||
// method: 'POST',
|
||||
// headers: {'Content-Type': 'application/json',},
|
||||
// body: JSON.stringify(data),
|
||||
// };
|
||||
// try {
|
||||
// const response = await fetch(p2postApiUrl+"/posts", requestOptions);
|
||||
// if (!response.ok) {
|
||||
// const errorText = await response.text();
|
||||
// throw new Error(`Error fetching posts: ${response.status} - ${errorText}`);
|
||||
// }
|
||||
|
||||
// const result = await response.json();
|
||||
// return result;
|
||||
|
||||
// } catch (error) {
|
||||
// console.error("Error fetching posts:", error);
|
||||
// throw error;
|
||||
// }
|
||||
|
||||
// },
|
||||
|
||||
// async uploadFile(file) {
|
||||
// const formData = new FormData();
|
||||
// formData.append("file", file);
|
||||
|
||||
// try {
|
||||
// const response = await fetch(p2postApiUrl + "/medias", {
|
||||
// method: "POST",
|
||||
// body: formData,
|
||||
// });
|
||||
|
||||
// if (!response.ok) {
|
||||
// const errorText = await response.text();
|
||||
// throw new Error(`Error uploading file: ${response.status} - ${errorText}`);
|
||||
// }
|
||||
|
||||
// const result = await response.json();
|
||||
// return result;
|
||||
|
||||
// } catch (error) {
|
||||
// console.error("Error uploading file:", error);
|
||||
// throw error;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
12
p2private/vue/api/socketEvents.js
Normal file
12
p2private/vue/api/socketEvents.js
Normal file
@@ -0,0 +1,12 @@
|
||||
|
||||
export default function registerSocketEvents(socket) {
|
||||
socket.on('connect', (data) => {
|
||||
console.log('Connected p2Private')
|
||||
})
|
||||
|
||||
// socket.on('newPost', (data) => {
|
||||
// p2postStore.addPost(data)
|
||||
// console.log('New Post', data)
|
||||
// })
|
||||
|
||||
}
|
||||
7
p2private/vue/router.js
Normal file
7
p2private/vue/router.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import HomeView from "./views/HomeView.vue";
|
||||
|
||||
const routes = [
|
||||
{path: '/', name:'p2private', component: HomeView},
|
||||
]
|
||||
|
||||
export {routes};
|
||||
254
p2private/vue/views/HomeView.vue
Normal file
254
p2private/vue/views/HomeView.vue
Normal file
@@ -0,0 +1,254 @@
|
||||
<script setup>
|
||||
import Button from '@/components/buttons/Button.vue';
|
||||
import Card from '@/components/cards/Card.vue';
|
||||
import InputComboBox from '@/components/inputs/InputComboBox.vue';
|
||||
import InputText from '@/components/inputs/InputText.vue';
|
||||
import Label from '@/components/labels/Label.vue';
|
||||
import { CheckIcon, DocumentTextIcon, EllipsisHorizontalIcon, GlobeAltIcon, LockClosedIcon, PaperAirplaneIcon, PaperClipIcon, ShieldCheckIcon, ShieldExclamationIcon, UserPlusIcon, UsersIcon, WifiIcon } from '@heroicons/vue/24/solid';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { p2privateApi } from '../api/p2privateApi';
|
||||
import { p2postApi } from '@/modules/p2post/api/p2postApi';
|
||||
|
||||
// const friends = ref([, {nickname:'Nickname2', unreadCount:2, publicKey:'1234aed233423c', isOnline:false, lastSeen:new Date()}])
|
||||
const friends = ref([])
|
||||
const activeFriend = ref(null)
|
||||
const myUsers = ref([])
|
||||
|
||||
const relays = ref([])
|
||||
|
||||
async function getRelays(){
|
||||
relays.value = await p2privateApi.getNetworks()
|
||||
}
|
||||
|
||||
async function getFriends(){
|
||||
const result = await p2privateApi.getFriends()
|
||||
for (const f of result){
|
||||
const m1 = {id:1, senderId:'me', content:"test Meeeeeeeeeeeeeeeeeeeeee", timestamp:new Date(), status:"SENT"}
|
||||
const m2 = {id:2, senderId:'peer', content:"test Peer", status:"RECEIVED", timestamp:new Date(), files:[{name:'file1', type:'image/jpeg', size:123}]}
|
||||
const mes = [m1,m2]
|
||||
const fr = {nickname:'Nickname', unreadCount:5, publicKey:f.pubkey, isOnline:true, messages:mes}
|
||||
friends.value.push(fr)
|
||||
}
|
||||
}
|
||||
|
||||
async function getMyUsers(){
|
||||
myUsers.value = await p2postApi.getMyUsers()
|
||||
}
|
||||
|
||||
const inputMessageText = ref("")
|
||||
async function createMessage(){
|
||||
const randomIndex = Math.floor(Math.random() * myUsers.value.length);
|
||||
const randomUser = myUsers.value[randomIndex]
|
||||
await p2privateApi.createMessage(randomUser.pubkey, activeFriend.value.publicKey, inputMessageText.value, [])
|
||||
}
|
||||
|
||||
onMounted(()=>{
|
||||
getRelays()
|
||||
getFriends()
|
||||
getMyUsers()
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="min-h-screen bg-black text-yellow-400">
|
||||
<header class="border-b border-yellow-400/20 py-6">
|
||||
<div class="container mx-auto px-6">
|
||||
<div class="flex items-center space-x-4">
|
||||
<div class="text-3xl font-black">
|
||||
<span class="text-yellow-400">P2</span>
|
||||
<span class="text-white">PRIVATE</span>
|
||||
</div>
|
||||
<div class="w-1 h-8 bg-yellow-400"></div>
|
||||
<span class="text-gray-400">Encrypted Peer-to-Peer Messaging</span>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="flex h-screen">
|
||||
|
||||
<!-- Sidebar -->
|
||||
<div class="w-80 border-r border-yellow-400/20 flex flex-col">
|
||||
|
||||
<!-- Add Peer -->
|
||||
<Card class="m-4 border-yellow-400/20 p-4">
|
||||
<h3 class="text-lg font-bold text-yellow-400 mb-4 flex items-center">
|
||||
<UserPlusIcon class="h-5 w-5 mr-2" />
|
||||
Add Friend
|
||||
</h3>
|
||||
|
||||
<div class="space-y-3">
|
||||
<div>
|
||||
<Label class="text-gray-300 mb-2 block text-sm">Public Key</Label>
|
||||
<InputText class="bg-black border-yellow-400/30 text-white font-mono text-sm" placeholder="Public Key"/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label class="text-gray-300 mb-2 block text-sm">Relay Networks</Label>
|
||||
<div class="space-y-2">
|
||||
<div v-for="relay of relays" class="flex items-center space-x-2">
|
||||
<input type="checkbox" :id="relay.id" name="options" :value="relay.name">
|
||||
<Label class="text-white text-sm">
|
||||
{{relay.name}}
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button @click="addFriend" class="w-full bg-yellow-400 hover:bg-yellow-300">
|
||||
<UserPlusIcon class="h-5 w-5 mr-2 text-black" />
|
||||
<span class="text-black">Add Peer</span>
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<!-- Friends list -->
|
||||
<div class="flex-1 overflow-hidden flex flex-col">
|
||||
<div class="px-4 py-2 border-b border-yellow-400/20">
|
||||
<h3 class="text-lg font-bold text-yellow-400 flex items-center">
|
||||
<UsersIcon class="w-5 h-5 mr-2" />
|
||||
Friends ({{friends.length}})
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 overflow-y-auto p-4 space-y-2">
|
||||
<div v-for="friend in friends">
|
||||
<div class="flex items-center space-x-3" @click="activeFriend = friend" :class="friend === activeFriend ? 'border rounded-lg border-yellow-400/30 p-1' : 'p-3'">
|
||||
<div class="relative">
|
||||
<img src="" alt="" class="w-12 h-12 rounded-full" />
|
||||
<div class="absolute -bottom-1 -right-1 w-4 h-4 rounded-full border-2 border-gray-500"
|
||||
:class="{'bg-green-400' : true}"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-white font-semibold truncate">{{friend.nickname}}</span>
|
||||
<span v-if="friend.unreadCount>0" class="bg-yellow-400 text-black text-xs font-bold px-2 py-1 rounded-full">
|
||||
{{friend.unreadCount}}
|
||||
</span>
|
||||
</div>
|
||||
<p class="text-xs text-gray-400 font-mono truncate">{{friend.publicKey}}</p>
|
||||
<div class="flex items-center space-x-1 mt-1 text-xs text-gray-500">
|
||||
<div v-if="friend.isOnline" class="flex flex-row">
|
||||
<WifiIcon class="w-5 h-5 text-green-400" />
|
||||
Online
|
||||
</div>
|
||||
<div v-if="!friend.isOnline" class="flex flex-row">
|
||||
<WifiIcon class="h-5 w-5 text-gray-400" />
|
||||
<!-- Last seen {{ friend.lastSeen.toLocaleTimeString() }} -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Chat -->
|
||||
<div v-if="activeFriend" class="flex-1 flex flex-col">
|
||||
<!-- Chat Header -->
|
||||
<div class="border-b border-yellow-400/20 p-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center space-x-3">
|
||||
<div class="relative">
|
||||
<img
|
||||
src=""
|
||||
alt=""
|
||||
class="w-10 h-10 rounded-full"
|
||||
/>
|
||||
<div class="absolute -bottom-1 -right-1 w-3 h-3 rounded-full border-2 border-gray-500"
|
||||
:class="{'bg-green-400':activeFriend.isOnline}">
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-white font-semibold">{{activeFriend.nickname}}</h3>
|
||||
<p class="text-xs text-gray-400 font-mono">{{activeFriend.publicKey}}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<div class="flex items-center space-x-1 text-green-400">
|
||||
<LockClosedIcon class="h-5 w-5" />
|
||||
<span class="text-sm">End-to-End Encrypted</span>
|
||||
</div>
|
||||
<div class="flex items-center space-x-1 text-yellow-400">
|
||||
<GlobeAltIcon class="h-5 w-5"/>
|
||||
<span class="text-sm">Via Network Relay</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Messages -->
|
||||
<div class="flex-1 overflow-y-auto p-4 space-y-4">
|
||||
<div v-for="message in activeFriend.messages"
|
||||
:key="message.id"
|
||||
class="flex justify-start"
|
||||
:class="{'justify-end':message.senderId=='me'}"
|
||||
>
|
||||
<!-- Message Content -->
|
||||
<div class="max-w-xs lg:max-w-md px-4 py-2 rounded-lg" :class="message.senderId == 'me' ? 'bg-yellow-400 text-black' : 'bg-gray-900 text-white'">
|
||||
|
||||
{{message.content}}
|
||||
|
||||
<!-- Files -->
|
||||
<div class="space-y-2 mb-2">
|
||||
<div v-for="file in message.files" class="flex items-center space-x-2 p-2 rounded">
|
||||
<Button class="p-1 h-auto hover:bg-black/20 gap-2">
|
||||
<DocumentTextIcon class="w-5 h-5" />
|
||||
<p class=" text-s">{{file.name}}</p>
|
||||
<p class="text-xs opacity-70">{{file.size}}</p>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Message Footer -->
|
||||
<div class="flex items-center justify-between text-xs opacity-80">
|
||||
<span>{{new Date(message.timestamp).toLocaleTimeString()}}</span>
|
||||
<div class="flex items-center space-x-1">
|
||||
<ShieldCheckIcon v-if="true" class="w-4 h-4"/>
|
||||
<ShieldExclamationIcon v-else class="w-4 h-4"/>
|
||||
<CheckIcon v-if="true" class="w-4 h-4"/>
|
||||
<EllipsisHorizontalIcon v-else class="w-4 h-4"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Message Input -->
|
||||
<div class="sticky bottom-0 border-t border-yellow-400/20 p-4">
|
||||
<!-- File Preview TODO from create post preview-->
|
||||
<div v-if="false" class="mb-3 p-3 bg-black border border-yellow-400/20 rounded-lg">
|
||||
</div>
|
||||
|
||||
<div class="flex items-end space-x-2">
|
||||
<Button class="border border-yellow-400 text-yellow-400 hover:bg-yellow-400 hover:text-black bg-transparent">
|
||||
Add Files
|
||||
</Button>
|
||||
|
||||
<div class="flex-1">
|
||||
<InputText class="bg-black border-yellow-400/30 text-white resize-none" v-model="inputMessageText"/>
|
||||
</div>
|
||||
|
||||
<Button @click="createMessage()" class="bg-yellow-400 text-black hover:bg-yellow-300">
|
||||
<!-- <Send size={16} /> -->
|
||||
Send
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- No Chat Selected -->
|
||||
<div v-if="!activeFriend" class="flex-1 flex items-center justify-center">
|
||||
<div class="text-center">
|
||||
<LockClosedIcon class="w-10 h-10 text-gray-600 mx-auto mb-4" />
|
||||
<h3 class="text-xl font-semibold text-gray-400 mb-2">Select a friend to start chatting</h3>
|
||||
<p class="text-gray-500">Your messages are end-to-end encrypted and decentralized</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user