Rust implementation of NextGraph, a Decentralized and local-first web 3.0 ecosystem
https://nextgraph.org
byzantine-fault-tolerancecrdtsdappsdecentralizede2eeeventual-consistencyjson-ldlocal-firstmarkdownocapoffline-firstp2pp2p-networkprivacy-protectionrdfrich-text-editorself-hostedsemantic-websparqlweb3collaboration
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
939 lines
28 KiB
939 lines
28 KiB
// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
|
|
// All rights reserved.
|
|
// Licensed under the Apache License, Version 2.0
|
|
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
|
|
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
|
// at your option. All files in the project carrying such
|
|
// notice may not be copied, modified, or distributed except
|
|
// according to those terms.
|
|
|
|
use std::collections::HashMap;
|
|
use std::fs::write;
|
|
|
|
use async_std::stream::StreamExt;
|
|
use oxrdf::Triple;
|
|
use serde::{Deserialize, Serialize};
|
|
use serde_json::Value;
|
|
use sys_locale::get_locales;
|
|
use tauri::scope::ipc::RemoteDomainAccessScope;
|
|
use tauri::utils::config::WindowConfig;
|
|
use tauri::{path::BaseDirectory, App, Manager};
|
|
use zeroize::Zeroize;
|
|
|
|
use ng_repo::errors::NgError;
|
|
use ng_repo::log::*;
|
|
use ng_repo::types::*;
|
|
use ng_repo::utils::decode_key;
|
|
|
|
use ng_net::app_protocol::*;
|
|
use ng_net::types::{ClientInfo, CreateAccountBSP, Invitation};
|
|
use ng_net::utils::{decode_invitation_string, spawn_and_log_error, Receiver, ResultSend};
|
|
|
|
use ng_wallet::types::*;
|
|
use ng_wallet::*;
|
|
|
|
use nextgraph::local_broker::*;
|
|
|
|
#[cfg(mobile)]
|
|
mod mobile;
|
|
#[cfg(mobile)]
|
|
pub use mobile::*;
|
|
|
|
pub type SetupHook = Box<dyn FnOnce(&mut App) -> Result<(), Box<dyn std::error::Error>> + Send>;
|
|
|
|
#[tauri::command(rename_all = "snake_case")]
|
|
async fn locales() -> Result<Vec<String>, ()> {
|
|
Ok(get_locales()
|
|
.filter_map(|lang| {
|
|
if lang == "C" || lang == "c" {
|
|
None
|
|
} else {
|
|
let mut split = lang.split('.');
|
|
let code = split.next().unwrap();
|
|
let code = code.replace("_", "-");
|
|
let mut split = code.rsplitn(2, '-');
|
|
let country = split.next().unwrap();
|
|
Some(match split.next() {
|
|
Some(next) => format!("{}-{}", next, country.to_uppercase()),
|
|
None => country.to_string(),
|
|
})
|
|
}
|
|
})
|
|
.collect())
|
|
}
|
|
|
|
#[tauri::command(rename_all = "snake_case")]
|
|
async fn test(app: tauri::AppHandle) -> Result<(), ()> {
|
|
let path = app
|
|
.path()
|
|
.resolve("", BaseDirectory::AppLocalData)
|
|
.map_err(|_| NgError::SerializationError)
|
|
.unwrap();
|
|
init_local_broker(Box::new(move || LocalBrokerConfig::BasePath(path.clone()))).await;
|
|
|
|
//log_debug!("test is {}", BROKER.read().await.test());
|
|
// let path = app
|
|
// .path()
|
|
// .resolve("storage", BaseDirectory::AppLocalData)
|
|
// .map_err(|_| ())?;
|
|
|
|
//BROKER.read().await.test_storage(path);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tauri::command(rename_all = "snake_case")]
|
|
async fn wallet_gen_shuffle_for_pazzle_opening(pazzle_length: u8) -> Result<ShuffledPazzle, ()> {
|
|
// log_debug!(
|
|
// "wallet_gen_shuffle_for_pazzle_opening from rust {}",
|
|
// pazzle_length
|
|
// );
|
|
Ok(gen_shuffle_for_pazzle_opening(pazzle_length))
|
|
}
|
|
|
|
#[tauri::command(rename_all = "snake_case")]
|
|
async fn wallet_gen_shuffle_for_pin() -> Result<Vec<u8>, ()> {
|
|
//log_debug!("wallet_gen_shuffle_for_pin from rust");
|
|
Ok(gen_shuffle_for_pin())
|
|
}
|
|
|
|
#[tauri::command(rename_all = "snake_case")]
|
|
async fn wallet_open_with_pazzle(
|
|
wallet: Wallet,
|
|
pazzle: Vec<u8>,
|
|
pin: [u8; 4],
|
|
_app: tauri::AppHandle,
|
|
) -> Result<SensitiveWallet, String> {
|
|
//log_debug!("wallet_open_with_pazzle from rust {:?}", pazzle);
|
|
let wallet = nextgraph::local_broker::wallet_open_with_pazzle(&wallet, pazzle, pin)
|
|
.map_err(|e| e.to_string())?;
|
|
Ok(wallet)
|
|
}
|
|
|
|
#[tauri::command(rename_all = "snake_case")]
|
|
async fn wallet_open_with_mnemonic(
|
|
wallet: Wallet,
|
|
mnemonic: [u16; 12],
|
|
pin: [u8; 4],
|
|
_app: tauri::AppHandle,
|
|
) -> Result<SensitiveWallet, String> {
|
|
let wallet =
|
|
ng_wallet::open_wallet_with_mnemonic(&wallet, mnemonic, pin).map_err(|e| e.to_string())?;
|
|
Ok(wallet)
|
|
}
|
|
|
|
#[tauri::command(rename_all = "snake_case")]
|
|
async fn wallet_open_with_mnemonic_words(
|
|
wallet: Wallet,
|
|
mnemonic_words: Vec<String>,
|
|
pin: [u8; 4],
|
|
_app: tauri::AppHandle,
|
|
) -> Result<SensitiveWallet, String> {
|
|
let wallet =
|
|
nextgraph::local_broker::wallet_open_with_mnemonic_words(&wallet, &mnemonic_words, pin)
|
|
.map_err(|e| e.to_string())?;
|
|
Ok(wallet)
|
|
}
|
|
|
|
#[tauri::command(rename_all = "snake_case")]
|
|
async fn wallet_get_file(wallet_name: String, app: tauri::AppHandle) -> Result<(), String> {
|
|
let ser = nextgraph::local_broker::wallet_get_file(&wallet_name)
|
|
.await
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
// save wallet file to Downloads folder
|
|
let path = app
|
|
.path()
|
|
.resolve(
|
|
format!("wallet-{}.ngw", wallet_name),
|
|
BaseDirectory::Download,
|
|
)
|
|
.unwrap();
|
|
write(path, &ser).map_err(|e| e.to_string())?;
|
|
Ok(())
|
|
}
|
|
|
|
#[tauri::command(rename_all = "snake_case")]
|
|
async fn wallet_create(
|
|
mut params: CreateWalletV0,
|
|
app: tauri::AppHandle,
|
|
) -> Result<CreateWalletResultV0, String> {
|
|
//log_debug!("wallet_create from rust {:?}", params);
|
|
params.result_with_wallet_file = !params.local_save;
|
|
let local_save = params.local_save;
|
|
let pdf = params.pdf;
|
|
let mut cwr = nextgraph::local_broker::wallet_create_v0(params)
|
|
.await
|
|
.map_err(|e| e.to_string())?;
|
|
if !local_save {
|
|
// save wallet file to Downloads folder
|
|
let path = app
|
|
.path()
|
|
.resolve(
|
|
format!("wallet-{}.ngw", cwr.wallet_name),
|
|
BaseDirectory::Download,
|
|
)
|
|
.unwrap();
|
|
let _r = write(path, &cwr.wallet_file);
|
|
cwr.wallet_file.zeroize();
|
|
cwr.wallet_file = vec![];
|
|
}
|
|
if pdf {
|
|
// save pdf file to Downloads folder
|
|
let path = app
|
|
.path()
|
|
.resolve(
|
|
format!("wallet-{}.pdf", cwr.wallet_name),
|
|
BaseDirectory::Download,
|
|
)
|
|
.unwrap();
|
|
let _r = write(path, &cwr.pdf_file);
|
|
cwr.pdf_file.zeroize();
|
|
cwr.pdf_file = vec![];
|
|
}
|
|
Ok(cwr)
|
|
}
|
|
|
|
#[tauri::command(rename_all = "snake_case")]
|
|
async fn wallet_read_file(file: Vec<u8>, _app: tauri::AppHandle) -> Result<Wallet, String> {
|
|
nextgraph::local_broker::wallet_read_file(file)
|
|
.await
|
|
.map_err(|e: NgError| e.to_string())
|
|
}
|
|
|
|
#[tauri::command(rename_all = "snake_case")]
|
|
async fn wallet_was_opened(
|
|
opened_wallet: SensitiveWallet,
|
|
_app: tauri::AppHandle,
|
|
) -> Result<ClientV0, String> {
|
|
nextgraph::local_broker::wallet_was_opened(opened_wallet)
|
|
.await
|
|
.map_err(|e: NgError| e.to_string())
|
|
}
|
|
|
|
#[tauri::command(rename_all = "snake_case")]
|
|
async fn wallet_import(
|
|
encrypted_wallet: Wallet,
|
|
opened_wallet: SensitiveWallet,
|
|
in_memory: bool,
|
|
_app: tauri::AppHandle,
|
|
) -> Result<ClientV0, String> {
|
|
nextgraph::local_broker::wallet_import(encrypted_wallet, opened_wallet, in_memory)
|
|
.await
|
|
.map_err(|e: NgError| e.to_string())
|
|
}
|
|
|
|
#[tauri::command(rename_all = "snake_case")]
|
|
async fn wallet_export_rendezvous(
|
|
session_id: u64,
|
|
code: String,
|
|
_app: tauri::AppHandle,
|
|
) -> Result<(), String> {
|
|
nextgraph::local_broker::wallet_export_rendezvous(session_id, code)
|
|
.await
|
|
.map_err(|e: NgError| e.to_string())
|
|
}
|
|
|
|
#[tauri::command(rename_all = "snake_case")]
|
|
async fn wallet_export_get_qrcode(
|
|
session_id: u64,
|
|
size: u32,
|
|
_app: tauri::AppHandle,
|
|
) -> Result<String, String> {
|
|
nextgraph::local_broker::wallet_export_get_qrcode(session_id, size)
|
|
.await
|
|
.map_err(|e: NgError| e.to_string())
|
|
}
|
|
|
|
#[tauri::command(rename_all = "snake_case")]
|
|
async fn wallet_export_get_textcode(
|
|
session_id: u64,
|
|
_app: tauri::AppHandle,
|
|
) -> Result<String, String> {
|
|
nextgraph::local_broker::wallet_export_get_textcode(session_id)
|
|
.await
|
|
.map_err(|e: NgError| e.to_string())
|
|
}
|
|
|
|
#[tauri::command(rename_all = "snake_case")]
|
|
async fn wallet_import_rendezvous(
|
|
size: u32,
|
|
_app: tauri::AppHandle,
|
|
) -> Result<(String, String), String> {
|
|
nextgraph::local_broker::wallet_import_rendezvous(size)
|
|
.await
|
|
.map_err(|e: NgError| e.to_string())
|
|
}
|
|
|
|
#[tauri::command(rename_all = "snake_case")]
|
|
async fn wallet_import_from_code(code: String, _app: tauri::AppHandle) -> Result<Wallet, String> {
|
|
nextgraph::local_broker::wallet_import_from_code(code)
|
|
.await
|
|
.map_err(|e: NgError| e.to_string())
|
|
}
|
|
|
|
#[tauri::command(rename_all = "snake_case")]
|
|
async fn get_wallets(
|
|
app: tauri::AppHandle,
|
|
) -> Result<Option<HashMap<String, LocalWalletStorageV0>>, String> {
|
|
let path = app
|
|
.path()
|
|
.resolve("", BaseDirectory::AppLocalData)
|
|
.map_err(|_| NgError::SerializationError)
|
|
.unwrap();
|
|
init_local_broker(Box::new(move || LocalBrokerConfig::BasePath(path.clone()))).await;
|
|
|
|
let res = wallets_get_all().await.map_err(|e| {
|
|
log_err!("wallets_get_all error {}", e.to_string());
|
|
});
|
|
if res.is_ok() {
|
|
return Ok(Some(res.unwrap()));
|
|
}
|
|
Ok(None)
|
|
}
|
|
|
|
#[tauri::command(rename_all = "snake_case")]
|
|
async fn session_start(
|
|
wallet_name: String,
|
|
user: PubKey,
|
|
_app: tauri::AppHandle,
|
|
) -> Result<SessionInfoString, String> {
|
|
let config = SessionConfig::new_save(&user, &wallet_name);
|
|
nextgraph::local_broker::session_start(config)
|
|
.await
|
|
.map_err(|e: NgError| e.to_string())
|
|
.map(|s| s.into())
|
|
}
|
|
|
|
#[tauri::command(rename_all = "snake_case")]
|
|
async fn session_start_remote(
|
|
wallet_name: String,
|
|
user: PubKey,
|
|
peer_id: Option<PubKey>,
|
|
_app: tauri::AppHandle,
|
|
) -> Result<SessionInfoString, String> {
|
|
let config = SessionConfig::new_remote(&user, &wallet_name, peer_id);
|
|
nextgraph::local_broker::session_start(config)
|
|
.await
|
|
.map_err(|e: NgError| e.to_string())
|
|
.map(|s| s.into())
|
|
}
|
|
|
|
#[tauri::command(rename_all = "snake_case")]
|
|
async fn encode_create_account(payload: CreateAccountBSP) -> Result<String, ()> {
|
|
//log_debug!("{:?}", payload);
|
|
payload.encode().ok_or(())
|
|
}
|
|
|
|
#[tauri::command(rename_all = "snake_case")]
|
|
async fn open_window(
|
|
url: String,
|
|
label: String,
|
|
title: String,
|
|
app: tauri::AppHandle,
|
|
) -> Result<(), ()> {
|
|
log_debug!("open window url {:?}", url);
|
|
let _already_exists = app.get_window(&label);
|
|
#[cfg(desktop)]
|
|
if _already_exists.is_some() {
|
|
let _ = _already_exists.unwrap().close();
|
|
std::thread::sleep(std::time::Duration::from_secs(1));
|
|
}
|
|
let mut config = WindowConfig::default();
|
|
config.label = label;
|
|
config.url = tauri::WindowUrl::External(url.parse().unwrap());
|
|
config.title = title;
|
|
let _register_window = tauri::WindowBuilder::from_config(&app, config)
|
|
.build()
|
|
.unwrap();
|
|
Ok(())
|
|
}
|
|
|
|
#[tauri::command(rename_all = "snake_case")]
|
|
async fn decode_invitation(invite: String) -> Option<Invitation> {
|
|
decode_invitation_string(invite)
|
|
}
|
|
|
|
#[tauri::command(rename_all = "snake_case")]
|
|
async fn file_get(
|
|
session_id: u64,
|
|
stream_id: &str,
|
|
reference: BlockRef,
|
|
branch_nuri: String,
|
|
app: tauri::AppHandle,
|
|
) -> Result<(), String> {
|
|
let branch_nuri =
|
|
NuriV0::new_from(&branch_nuri).map_err(|e| format!("branch_nuri: {}", e.to_string()))?;
|
|
let mut nuri = NuriV0::new_from_obj_ref(&reference);
|
|
nuri.copy_target_from(&branch_nuri);
|
|
|
|
let mut request = AppRequest::new(AppRequestCommandV0::FileGet, nuri, None);
|
|
request.set_session_id(session_id);
|
|
|
|
app_request_stream(request, stream_id, app).await
|
|
}
|
|
|
|
#[tauri::command(rename_all = "snake_case")]
|
|
async fn app_request_stream(
|
|
request: AppRequest,
|
|
stream_id: &str,
|
|
app: tauri::AppHandle,
|
|
) -> Result<(), String> {
|
|
//log_debug!("app request stream {} {:?}", stream_id, request);
|
|
let main_window = app.get_window("main").unwrap();
|
|
|
|
let reader;
|
|
{
|
|
let cancel;
|
|
(reader, cancel) = nextgraph::local_broker::app_request_stream(request)
|
|
.await
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
nextgraph::local_broker::tauri_stream_add(stream_id.to_string(), cancel)
|
|
.await
|
|
.map_err(|e| e.to_string())?;
|
|
}
|
|
|
|
async fn inner_task(
|
|
mut reader: Receiver<AppResponse>,
|
|
stream_id: String,
|
|
main_window: tauri::Window,
|
|
) -> ResultSend<()> {
|
|
while let Some(app_response) = reader.next().await {
|
|
let app_response = nextgraph::verifier::prepare_app_response_for_js(app_response)?;
|
|
main_window.emit(&stream_id, app_response).unwrap();
|
|
}
|
|
|
|
nextgraph::local_broker::tauri_stream_cancel(stream_id)
|
|
.await
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
//log_debug!("END OF LOOP");
|
|
Ok(())
|
|
}
|
|
|
|
spawn_and_log_error(inner_task(reader, stream_id.to_string(), main_window));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tauri::command(rename_all = "snake_case")]
|
|
async fn discrete_update(
|
|
session_id: u64,
|
|
update: serde_bytes::ByteBuf,
|
|
heads: Vec<String>,
|
|
crdt: String,
|
|
nuri: String,
|
|
) -> Result<(), String> {
|
|
let nuri = NuriV0::new_from(&nuri).map_err(|e| e.to_string())?;
|
|
|
|
let request = AppRequest::V0(AppRequestV0 {
|
|
command: AppRequestCommandV0::new_update(),
|
|
nuri,
|
|
payload: Some(
|
|
AppRequestPayload::new_discrete_update(heads, crdt, update.into_vec())
|
|
.map_err(|e| format!("Deserialization error of heads: {e}"))?,
|
|
),
|
|
session_id,
|
|
});
|
|
|
|
let res = nextgraph::local_broker::app_request(request)
|
|
.await
|
|
.map_err(|e: NgError| e.to_string())?;
|
|
if let AppResponse::V0(AppResponseV0::Error(e)) = res {
|
|
Err(e)
|
|
} else {
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[tauri::command(rename_all = "snake_case")]
|
|
async fn file_save_to_downloads(
|
|
session_id: u64,
|
|
reference: ObjectRef,
|
|
filename: String,
|
|
branch_nuri: String,
|
|
app: tauri::AppHandle,
|
|
) -> Result<(), String> {
|
|
let branch_nuri =
|
|
NuriV0::new_from(&branch_nuri).map_err(|e| format!("branch_nuri: {}", e.to_string()))?;
|
|
let mut nuri = NuriV0::new_from_obj_ref(&reference);
|
|
nuri.copy_target_from(&branch_nuri);
|
|
|
|
let mut request = AppRequest::new(AppRequestCommandV0::FileGet, nuri, None);
|
|
request.set_session_id(session_id);
|
|
|
|
let (mut reader, _cancel) = nextgraph::local_broker::app_request_stream(request)
|
|
.await
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
let mut file_vec: Vec<u8> = vec![];
|
|
while let Some(app_response) = reader.next().await {
|
|
match app_response {
|
|
AppResponse::V0(AppResponseV0::FileMeta(filemeta)) => {
|
|
file_vec = Vec::with_capacity(filemeta.size as usize);
|
|
}
|
|
AppResponse::V0(AppResponseV0::FileBinary(mut bin)) => {
|
|
if !bin.is_empty() {
|
|
file_vec.append(&mut bin);
|
|
}
|
|
}
|
|
AppResponse::V0(AppResponseV0::EndOfStream) => break,
|
|
_ => return Err("invalid response".to_string()),
|
|
}
|
|
}
|
|
|
|
let mut i: usize = 0;
|
|
loop {
|
|
let dest_filename = if i == 0 {
|
|
filename.clone()
|
|
} else {
|
|
filename
|
|
.rsplit_once(".")
|
|
.map(|(l, r)| format!("{l} ({}).{r}", i.to_string()))
|
|
.or_else(|| Some(format!("{filename} ({})", i.to_string())))
|
|
.unwrap()
|
|
};
|
|
|
|
let path = app
|
|
.path()
|
|
.resolve(dest_filename, BaseDirectory::Download)
|
|
.unwrap();
|
|
|
|
if path.exists() {
|
|
i = i + 1;
|
|
} else {
|
|
write(path, &file_vec).map_err(|e| e.to_string())?;
|
|
break;
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
#[tauri::command(rename_all = "snake_case")]
|
|
async fn doc_fetch_private_subscribe() -> Result<AppRequest, String> {
|
|
let request = AppRequest::new(
|
|
AppRequestCommandV0::Fetch(AppFetchContentV0::get_or_subscribe(true)),
|
|
NuriV0::new_private_store_target(),
|
|
None,
|
|
);
|
|
Ok(request)
|
|
}
|
|
|
|
#[tauri::command(rename_all = "snake_case")]
|
|
async fn doc_fetch_repo_subscribe(repo_o: String) -> Result<AppRequest, String> {
|
|
let request = AppRequest::new(
|
|
AppRequestCommandV0::Fetch(AppFetchContentV0::get_or_subscribe(true)),
|
|
NuriV0::new_from(&repo_o).map_err(|e| e.to_string())?,
|
|
None,
|
|
);
|
|
Ok(request)
|
|
}
|
|
|
|
#[tauri::command(rename_all = "snake_case")]
|
|
async fn branch_history(session_id: u64, nuri: String) -> Result<AppHistoryJs, String> {
|
|
let request = AppRequest::V0(AppRequestV0 {
|
|
command: AppRequestCommandV0::new_history(),
|
|
nuri: NuriV0::new_from(&nuri).map_err(|e| e.to_string())?,
|
|
payload: None,
|
|
session_id,
|
|
});
|
|
|
|
let res = nextgraph::local_broker::app_request(request)
|
|
.await
|
|
.map_err(|e: NgError| e.to_string())?;
|
|
|
|
let AppResponse::V0(res) = res;
|
|
//log_debug!("{:?}", res);
|
|
match res {
|
|
AppResponseV0::History(s) => Ok(s.to_js()),
|
|
_ => Err("invalid response".to_string()),
|
|
}
|
|
}
|
|
|
|
#[tauri::command(rename_all = "snake_case")]
|
|
async fn sparql_update(
|
|
session_id: u64,
|
|
sparql: String,
|
|
nuri: Option<String>,
|
|
) -> Result<(), String> {
|
|
let (nuri, base) = if let Some(n) = nuri {
|
|
let nuri = NuriV0::new_from(&n).map_err(|e| e.to_string())?;
|
|
let b = nuri.repo();
|
|
(nuri, Some(b))
|
|
} else {
|
|
(NuriV0::new_private_store_target(), None)
|
|
};
|
|
|
|
let request = AppRequest::V0(AppRequestV0 {
|
|
command: AppRequestCommandV0::new_write_query(),
|
|
nuri,
|
|
payload: Some(AppRequestPayload::new_sparql_query(sparql, base)),
|
|
session_id,
|
|
});
|
|
|
|
let res = nextgraph::local_broker::app_request(request)
|
|
.await
|
|
.map_err(|e: NgError| e.to_string())?;
|
|
if let AppResponse::V0(AppResponseV0::Error(e)) = res {
|
|
Err(e)
|
|
} else {
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[tauri::command(rename_all = "snake_case")]
|
|
async fn sparql_query(
|
|
session_id: u64,
|
|
sparql: String,
|
|
base: Option<String>,
|
|
nuri: Option<String>,
|
|
) -> Result<Value, String> {
|
|
let nuri = if nuri.is_some() {
|
|
NuriV0::new_from(&nuri.unwrap()).map_err(|e| e.to_string())?
|
|
} else {
|
|
NuriV0::new_entire_user_site()
|
|
};
|
|
|
|
let request = AppRequest::V0(AppRequestV0 {
|
|
command: AppRequestCommandV0::new_read_query(),
|
|
nuri,
|
|
payload: Some(AppRequestPayload::new_sparql_query(sparql, base)),
|
|
session_id,
|
|
});
|
|
|
|
let response = nextgraph::local_broker::app_request(request)
|
|
.await
|
|
.map_err(|e: NgError| e.to_string())?;
|
|
|
|
let AppResponse::V0(res) = response;
|
|
match res {
|
|
AppResponseV0::False => return Ok(Value::Bool(false)),
|
|
AppResponseV0::True => return Ok(Value::Bool(true)),
|
|
AppResponseV0::Graph(graph) => {
|
|
let triples: Vec<Triple> = serde_bare::from_slice(&graph)
|
|
.map_err(|_| "Deserialization error of graph".to_string())?;
|
|
|
|
Ok(Value::Array(
|
|
triples
|
|
.into_iter()
|
|
.map(|t| Value::String(t.to_string()))
|
|
.collect(),
|
|
))
|
|
}
|
|
AppResponseV0::QueryResult(buf) => {
|
|
let string = String::from_utf8(buf)
|
|
.map_err(|_| "Deserialization error of JSON QueryResult String".to_string())?;
|
|
Ok(serde_json::from_str(&string)
|
|
.map_err(|_| "Parsing error of JSON QueryResult String".to_string())?)
|
|
}
|
|
AppResponseV0::Error(e) => Err(e.to_string().into()),
|
|
_ => Err("invalid AppResponse".to_string().into()),
|
|
}
|
|
}
|
|
|
|
#[tauri::command(rename_all = "snake_case")]
|
|
async fn app_request(request: AppRequest) -> Result<AppResponse, String> {
|
|
//log_debug!("app request {:?}", request);
|
|
|
|
nextgraph::local_broker::app_request(request)
|
|
.await
|
|
.map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command(rename_all = "snake_case")]
|
|
async fn doc_create(
|
|
session_id: u64,
|
|
crdt: String,
|
|
class_name: String,
|
|
store_repo: StoreRepo,
|
|
destination: String,
|
|
) -> Result<String, String> {
|
|
let class = BranchCrdt::from(crdt, class_name).map_err(|e| e.to_string())?;
|
|
|
|
let destination = DocCreateDestination::from(destination).map_err(|e| e.to_string())?;
|
|
|
|
let request = AppRequest::V0(AppRequestV0 {
|
|
session_id,
|
|
command: AppRequestCommandV0::new_create(),
|
|
nuri: NuriV0::new_empty(),
|
|
payload: Some(AppRequestPayload::V0(AppRequestPayloadV0::Create(
|
|
DocCreate {
|
|
store: store_repo,
|
|
class,
|
|
destination,
|
|
},
|
|
))),
|
|
});
|
|
|
|
let response = nextgraph::local_broker::app_request(request)
|
|
.await
|
|
.map_err(|e: NgError| e.to_string())?;
|
|
|
|
if let AppResponse::V0(AppResponseV0::Nuri(nuri)) = response {
|
|
Ok(nuri)
|
|
} else {
|
|
Err("invalid response".to_string())
|
|
}
|
|
}
|
|
|
|
#[tauri::command(rename_all = "snake_case")]
|
|
async fn app_request_with_nuri_command(
|
|
nuri: String,
|
|
command: AppRequestCommandV0,
|
|
session_id: u64,
|
|
payload: Option<AppRequestPayloadV0>,
|
|
) -> Result<AppResponse, String> {
|
|
let nuri = NuriV0::new_from(&nuri).map_err(|e| e.to_string())?;
|
|
|
|
let payload = payload.map(|p| AppRequestPayload::V0(p));
|
|
|
|
let request = AppRequest::V0(AppRequestV0 {
|
|
session_id,
|
|
command,
|
|
nuri,
|
|
payload,
|
|
});
|
|
|
|
app_request(request).await
|
|
}
|
|
|
|
#[tauri::command(rename_all = "snake_case")]
|
|
async fn upload_chunk(
|
|
session_id: u64,
|
|
upload_id: u32,
|
|
chunk: serde_bytes::ByteBuf,
|
|
nuri: String,
|
|
_app: tauri::AppHandle,
|
|
) -> Result<AppResponse, String> {
|
|
//log_debug!("upload_chunk {:?}", chunk);
|
|
|
|
let mut request = AppRequest::new(
|
|
AppRequestCommandV0::FilePut,
|
|
NuriV0::new_from(&nuri).map_err(|e| e.to_string())?,
|
|
Some(AppRequestPayload::V0(
|
|
AppRequestPayloadV0::RandomAccessFilePutChunk((upload_id, chunk)),
|
|
)),
|
|
);
|
|
request.set_session_id(session_id);
|
|
|
|
nextgraph::local_broker::app_request(request)
|
|
.await
|
|
.map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command(rename_all = "snake_case")]
|
|
async fn cancel_stream(stream_id: &str) -> Result<(), String> {
|
|
//log_debug!("cancel stream {}", stream_id);
|
|
Ok(
|
|
nextgraph::local_broker::tauri_stream_cancel(stream_id.to_string())
|
|
.await
|
|
.map_err(|e: NgError| e.to_string())?,
|
|
)
|
|
}
|
|
|
|
#[tauri::command(rename_all = "snake_case")]
|
|
async fn disconnections_subscribe(app: tauri::AppHandle) -> Result<(), String> {
|
|
let path = app
|
|
.path()
|
|
.resolve("", BaseDirectory::AppLocalData)
|
|
.map_err(|_| NgError::SerializationError)
|
|
.unwrap();
|
|
init_local_broker(Box::new(move || LocalBrokerConfig::BasePath(path.clone()))).await;
|
|
|
|
let main_window = app.get_window("main").unwrap();
|
|
|
|
let reader = nextgraph::local_broker::take_disconnections_receiver()
|
|
.await
|
|
.map_err(|e: NgError| e.to_string())?;
|
|
|
|
async fn inner_task(
|
|
mut reader: Receiver<String>,
|
|
main_window: tauri::Window,
|
|
) -> ResultSend<()> {
|
|
while let Some(user_id) = reader.next().await {
|
|
log_debug!("DISCONNECTION FOR {user_id}");
|
|
main_window.emit("disconnections", user_id).unwrap();
|
|
}
|
|
log_debug!("END OF disconnections listener");
|
|
Ok(())
|
|
}
|
|
|
|
spawn_and_log_error(inner_task(reader, main_window));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tauri::command(rename_all = "snake_case")]
|
|
async fn session_stop(user_id: String) -> Result<(), String> {
|
|
let user_id = decode_key(&user_id).map_err(|_| "Invalid user_id")?;
|
|
nextgraph::local_broker::session_stop(&user_id)
|
|
.await
|
|
.map_err(|e: NgError| e.to_string())
|
|
}
|
|
|
|
#[tauri::command(rename_all = "snake_case")]
|
|
async fn user_disconnect(user_id: String) -> Result<(), String> {
|
|
let user_id = decode_key(&user_id).map_err(|_| "Invalid user_id")?;
|
|
nextgraph::local_broker::user_disconnect(&user_id)
|
|
.await
|
|
.map_err(|e: NgError| e.to_string())
|
|
}
|
|
|
|
#[tauri::command(rename_all = "snake_case")]
|
|
async fn wallet_close(wallet_name: String) -> Result<(), String> {
|
|
nextgraph::local_broker::wallet_close(&wallet_name)
|
|
.await
|
|
.map_err(|e: NgError| e.to_string())
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
struct ConnectionInfo {
|
|
pub server_id: String,
|
|
pub server_ip: String,
|
|
pub error: Option<String>,
|
|
pub since: u64,
|
|
}
|
|
|
|
#[tauri::command(rename_all = "snake_case")]
|
|
async fn user_connect(
|
|
info: ClientInfo,
|
|
user_id: String,
|
|
_location: Option<String>,
|
|
) -> Result<HashMap<String, ConnectionInfo>, String> {
|
|
let user_id = decode_key(&user_id).map_err(|_| "Invalid user_id")?;
|
|
let mut opened_connections: HashMap<String, ConnectionInfo> = HashMap::new();
|
|
|
|
let results = nextgraph::local_broker::user_connect_with_device_info(info, &user_id, None)
|
|
.await
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
log_debug!("{:?}", results);
|
|
|
|
for result in results {
|
|
opened_connections.insert(
|
|
result.0,
|
|
ConnectionInfo {
|
|
server_id: result.1,
|
|
server_ip: result.2,
|
|
error: result.3,
|
|
since: result.4 as u64,
|
|
},
|
|
);
|
|
}
|
|
|
|
Ok(opened_connections)
|
|
}
|
|
|
|
#[tauri::command(rename_all = "snake_case")]
|
|
fn client_info_rust() -> Result<Value, String> {
|
|
Ok(ng_repo::os_info::get_os_info())
|
|
}
|
|
|
|
#[tauri::command(rename_all = "snake_case")]
|
|
fn get_device_name() -> Result<String, String> {
|
|
Ok(nextgraph::get_device_name())
|
|
}
|
|
|
|
#[derive(Default)]
|
|
pub struct AppBuilder {
|
|
setup: Option<SetupHook>,
|
|
}
|
|
|
|
#[cfg(debug_assertions)]
|
|
const ALLOWED_BSP_DOMAINS: [&str; 2] = ["account-dev.nextgraph.eu", "account-dev.nextgraph.net"];
|
|
#[cfg(not(debug_assertions))]
|
|
const ALLOWED_BSP_DOMAINS: [&str; 2] = ["account.nextgraph.eu", "account.nextgraph.net"];
|
|
|
|
impl AppBuilder {
|
|
pub fn new() -> Self {
|
|
Self::default()
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn setup<F>(mut self, setup: F) -> Self
|
|
where
|
|
F: FnOnce(&mut App) -> Result<(), Box<dyn std::error::Error>> + Send + 'static,
|
|
{
|
|
self.setup.replace(Box::new(setup));
|
|
self
|
|
}
|
|
|
|
pub fn run(self) {
|
|
let setup = self.setup;
|
|
|
|
#[allow(unused_mut)]
|
|
let mut builder = tauri::Builder::default()
|
|
.setup(move |app| {
|
|
if let Some(setup) = setup {
|
|
(setup)(app)?;
|
|
}
|
|
|
|
for domain in ALLOWED_BSP_DOMAINS {
|
|
app.ipc_scope().configure_remote_access(
|
|
RemoteDomainAccessScope::new(domain)
|
|
.add_window("registration")
|
|
.add_window("main")
|
|
.add_plugins(["window", "event"]),
|
|
);
|
|
}
|
|
Ok(())
|
|
})
|
|
.plugin(tauri_plugin_window::init());
|
|
|
|
#[cfg(mobile)]
|
|
{
|
|
builder = builder.plugin(tauri_plugin_barcode_scanner::init());
|
|
}
|
|
|
|
builder
|
|
.invoke_handler(tauri::generate_handler![
|
|
test,
|
|
locales,
|
|
wallet_gen_shuffle_for_pazzle_opening,
|
|
wallet_gen_shuffle_for_pin,
|
|
wallet_open_with_pazzle,
|
|
wallet_open_with_mnemonic,
|
|
wallet_open_with_mnemonic_words,
|
|
wallet_was_opened,
|
|
wallet_create,
|
|
wallet_read_file,
|
|
wallet_get_file,
|
|
wallet_import,
|
|
wallet_export_rendezvous,
|
|
wallet_export_get_qrcode,
|
|
wallet_export_get_textcode,
|
|
wallet_import_rendezvous,
|
|
wallet_import_from_code,
|
|
wallet_close,
|
|
encode_create_account,
|
|
session_start,
|
|
session_start_remote,
|
|
session_stop,
|
|
get_wallets,
|
|
open_window,
|
|
decode_invitation,
|
|
disconnections_subscribe,
|
|
user_connect,
|
|
user_disconnect,
|
|
client_info_rust,
|
|
doc_fetch_private_subscribe,
|
|
doc_fetch_repo_subscribe,
|
|
doc_create,
|
|
cancel_stream,
|
|
discrete_update,
|
|
app_request_stream,
|
|
file_get,
|
|
file_save_to_downloads,
|
|
app_request,
|
|
app_request_with_nuri_command,
|
|
upload_chunk,
|
|
get_device_name,
|
|
sparql_query,
|
|
sparql_update,
|
|
branch_history,
|
|
])
|
|
.run(tauri::generate_context!())
|
|
.expect("error while running tauri application");
|
|
}
|
|
}
|
|
|