parent
495340dabe
commit
17e1eb95e3
@ -0,0 +1,152 @@ |
|||||||
|
<!-- |
||||||
|
// 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. |
||||||
|
--> |
||||||
|
|
||||||
|
<script lang="ts"> |
||||||
|
import { |
||||||
|
branch_subscribe, |
||||||
|
active_session, |
||||||
|
toast_error, |
||||||
|
toast_success, |
||||||
|
display_error, |
||||||
|
online, |
||||||
|
} from "../../store"; |
||||||
|
import { |
||||||
|
cur_tab, |
||||||
|
show_doc_popup |
||||||
|
} from "../../tab"; |
||||||
|
import { get } from "svelte/store"; |
||||||
|
import { onMount, onDestroy, tick } from "svelte"; |
||||||
|
import ng from "../../api"; |
||||||
|
import { t } from "svelte-i18n"; |
||||||
|
import { |
||||||
|
ShieldExclamation, |
||||||
|
ShieldCheck, |
||||||
|
Camera |
||||||
|
} from "svelte-heros-v2"; |
||||||
|
import { |
||||||
|
Toggle, |
||||||
|
Button |
||||||
|
} from "flowbite-svelte"; |
||||||
|
let is_tauri = import.meta.env.TAURI_PLATFORM; |
||||||
|
|
||||||
|
let heads = []; |
||||||
|
onMount(async ()=>{ |
||||||
|
heads = await ng.signature_status($active_session.session_id, "did:ng:"+$cur_tab.branch.nuri+":"+$cur_tab.store.overlay); |
||||||
|
}); |
||||||
|
let snapshot = false; |
||||||
|
let force_snapshot = false; |
||||||
|
let can_sign = false; |
||||||
|
let has_signatures = false; |
||||||
|
let hide_snapshot = false; |
||||||
|
$: force_snapshot = heads.every(h => h[1]) && heads.length && !heads[0][2]; |
||||||
|
$: can_sign = force_snapshot || !heads[0]?.[2] ; |
||||||
|
$: has_signatures = heads.some(h => h[1]); |
||||||
|
let cur_link; |
||||||
|
|
||||||
|
function signed_commit_link(head) { |
||||||
|
return `did:ng:${$cur_tab.branch.nuri}:${$cur_tab.store.overlay}:${head[1]}:${$cur_tab.store.has_outer}` |
||||||
|
} |
||||||
|
|
||||||
|
async function sign() { |
||||||
|
if (snapshot) await sign_snapshot(); |
||||||
|
else { |
||||||
|
try { |
||||||
|
let immediate = await ng.signature_request($active_session.session_id, "did:ng:"+$cur_tab.branch.nuri+":"+$cur_tab.store.overlay); |
||||||
|
if (immediate) { |
||||||
|
heads = await ng.signature_status($active_session.session_id, "did:ng:"+$cur_tab.branch.nuri+":"+$cur_tab.store.overlay); |
||||||
|
cur_link=signed_commit_link(heads[0]); |
||||||
|
hide_snapshot = true; |
||||||
|
toast_success($t("doc.signature_is_ready")); |
||||||
|
} else { |
||||||
|
$show_doc_popup = false; |
||||||
|
toast_success($t("doc.signature_is_on_its_way")); |
||||||
|
} |
||||||
|
} catch (e) { |
||||||
|
toast_error(display_error(e)); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
async function sign_snapshot() { |
||||||
|
try { |
||||||
|
let immediate = await ng.signed_snapshot_request($active_session.session_id, "did:ng:"+$cur_tab.branch.nuri+":"+$cur_tab.store.overlay); |
||||||
|
if (immediate) { |
||||||
|
heads = await ng.signature_status($active_session.session_id, "did:ng:"+$cur_tab.branch.nuri+":"+$cur_tab.store.overlay); |
||||||
|
} else { |
||||||
|
$show_doc_popup = false; |
||||||
|
toast_success($t("doc.signed_snapshot_is_on_its_way")); |
||||||
|
} |
||||||
|
} catch (e) { |
||||||
|
toast_error(display_error(e)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
</script> |
||||||
|
|
||||||
|
<div class="flex flex-col"> |
||||||
|
<span class="font-bold text-xl">Signature</span> |
||||||
|
|
||||||
|
Current heads : |
||||||
|
{#each heads as head} |
||||||
|
{#if head[1]} |
||||||
|
<div style="font-family: monospace; font: Courier; font-size:16px;" class="flex text-green-600 clickable my-2" |
||||||
|
on:click={()=>cur_link=signed_commit_link(head)} on:keypress={()=>cur_link=signed_commit_link(head)} tabindex="0" role="button"> |
||||||
|
<ShieldCheck tabindex="-1" class="w-5 h-5 mr-2" /> |
||||||
|
{head[0].substring(0,7)} |
||||||
|
</div> |
||||||
|
{:else} |
||||||
|
<div style="font-family: monospace; font: Courier; font-size:16px;" class="flex my-2"> |
||||||
|
<ShieldExclamation tabindex="-1" class="w-5 h-5 mr-2" /> |
||||||
|
{head[0].substring(0,7)} |
||||||
|
</div> |
||||||
|
{/if} |
||||||
|
{/each} |
||||||
|
{#if !hide_snapshot} |
||||||
|
{#if force_snapshot} |
||||||
|
<Button |
||||||
|
disabled={!$online && !is_tauri} |
||||||
|
on:click|once={sign_snapshot} |
||||||
|
on:keypress|once={sign_snapshot} |
||||||
|
class="select-none mt-2 mb-2 text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-500/50 rounded-lg text-base p-2 text-center inline-flex items-center dark:focus:ring-primary-700/55" |
||||||
|
> |
||||||
|
<ShieldCheck tabindex="-1" class="mr-2 focus:outline-none" /> |
||||||
|
{$t("doc.sign_snapshot")} |
||||||
|
</Button> |
||||||
|
<span class="mb-2">or click on one of the signed heads to get its link.</span> |
||||||
|
|
||||||
|
{:else if can_sign} |
||||||
|
<button |
||||||
|
on:click|once={sign} |
||||||
|
on:keypress|once={sign} |
||||||
|
class="shrink select-none mt-2 mb-3 text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-500/50 rounded-lg text-base p-2 text-center inline-flex items-center dark:focus:ring-primary-700/55" |
||||||
|
> |
||||||
|
<ShieldCheck tabindex="-1" class="mr-2 focus:outline-none" /> |
||||||
|
{$t("doc.sign_heads")} |
||||||
|
</button> |
||||||
|
<Toggle |
||||||
|
disabled={!$online && !is_tauri} |
||||||
|
class="clickable mb-3" |
||||||
|
bind:checked={ snapshot } |
||||||
|
><span class="text-gray-700 text-base">{$t("doc.take_snapshot")}</span> |
||||||
|
</Toggle> |
||||||
|
{#if has_signatures}<span>or click on one of the signed heads to get its link</span>{/if} |
||||||
|
{:else} |
||||||
|
<div class="flex mt-3"><Camera tabindex="-1" class="w-6 h-6 mr-3 text-green-600"/><span class="text-green-600">A signed snapshot is currently at the head.</span></div> |
||||||
|
<span>Here is its link that you can share.<br/>For now this link is only usable with the CLI, by running the following command :<br/><br/></span> |
||||||
|
<span style="font-family: monospace; font: Courier; font-size:16px;" class="break-all">ngcli get {signed_commit_link(heads[0])}</span> |
||||||
|
{/if} |
||||||
|
{/if} |
||||||
|
{#if (force_snapshot || can_sign) && cur_link } |
||||||
|
<span class="mt-3">For now the link is only usable with the CLI, by running the following command :<br/><br/></span> |
||||||
|
<span style="font-family: monospace; font: Courier; font-size:16px;" class="break-all">ngcli get {cur_link}</span> |
||||||
|
{/if} |
||||||
|
</div> |
||||||
|
|
||||||
|
|
@ -0,0 +1,121 @@ |
|||||||
|
// 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 crate::verifier::Verifier; |
||||||
|
use ng_net::app_protocol::NuriTargetV0; |
||||||
|
use ng_oxigraph::oxigraph::sparql::{Query, QueryResults}; |
||||||
|
use ng_repo::errors::{StorageError, VerifierError}; |
||||||
|
use ng_repo::types::*; |
||||||
|
use serde_json::json; |
||||||
|
use yrs::types::ToJson; |
||||||
|
use yrs::updates::decoder::Decode; |
||||||
|
use yrs::{GetString, Transact}; |
||||||
|
|
||||||
|
impl Verifier { |
||||||
|
pub(crate) fn take_snapshot( |
||||||
|
&self, |
||||||
|
crdt: &BranchCrdt, |
||||||
|
branch_id: &BranchId, |
||||||
|
target: &NuriTargetV0, |
||||||
|
) -> Result<String, VerifierError> { |
||||||
|
let state = match self |
||||||
|
.user_storage |
||||||
|
.as_ref() |
||||||
|
.unwrap() |
||||||
|
.branch_get_discrete_state(branch_id) |
||||||
|
{ |
||||||
|
Ok(s) => Ok(s), |
||||||
|
Err(StorageError::NoDiscreteState) => Ok(vec![]), |
||||||
|
Err(e) => Err(e), |
||||||
|
}?; |
||||||
|
|
||||||
|
let discrete = if state.is_empty() { |
||||||
|
serde_json::Value::Null |
||||||
|
} else { |
||||||
|
match crdt { |
||||||
|
BranchCrdt::Automerge(_) => { |
||||||
|
let doc = automerge::Automerge::load(&state) |
||||||
|
.map_err(|e| VerifierError::AutomergeError(e.to_string()))?; |
||||||
|
|
||||||
|
serde_json::json!(automerge::AutoSerde::from(&doc)) |
||||||
|
} |
||||||
|
BranchCrdt::YText(_) => { |
||||||
|
let doc = yrs::Doc::new(); |
||||||
|
let text = doc.get_or_insert_text("ng"); |
||||||
|
let mut txn = doc.transact_mut(); |
||||||
|
let update = yrs::Update::decode_v1(&state) |
||||||
|
.map_err(|e| VerifierError::YrsError(e.to_string()))?; |
||||||
|
txn.apply_update(update); |
||||||
|
serde_json::Value::from(text.get_string(&txn)) |
||||||
|
} |
||||||
|
BranchCrdt::YArray(_) => { |
||||||
|
let doc = yrs::Doc::new(); |
||||||
|
let array = doc.get_or_insert_array("ng"); |
||||||
|
let mut txn = doc.transact_mut(); |
||||||
|
let update = yrs::Update::decode_v1(&state) |
||||||
|
.map_err(|e| VerifierError::YrsError(e.to_string()))?; |
||||||
|
txn.apply_update(update); |
||||||
|
let mut json = String::new(); |
||||||
|
array.to_json(&txn).to_json(&mut json); |
||||||
|
|
||||||
|
serde_json::from_str(&json).map_err(|_| VerifierError::InvalidJson)? |
||||||
|
} |
||||||
|
BranchCrdt::YMap(_) => { |
||||||
|
let doc = yrs::Doc::new(); |
||||||
|
let map = doc.get_or_insert_map("ng"); |
||||||
|
let mut txn = doc.transact_mut(); |
||||||
|
let update = yrs::Update::decode_v1(&state) |
||||||
|
.map_err(|e| VerifierError::YrsError(e.to_string()))?; |
||||||
|
txn.apply_update(update); |
||||||
|
let mut json = String::new(); |
||||||
|
map.to_json(&txn).to_json(&mut json); |
||||||
|
serde_json::from_str(&json).map_err(|_| VerifierError::InvalidJson)? |
||||||
|
} |
||||||
|
BranchCrdt::YXml(_) => { |
||||||
|
// TODO: if it is markdown, output the markdown instead of XML
|
||||||
|
let doc = yrs::Doc::new(); |
||||||
|
let xml = doc.get_or_insert_xml_fragment("prosemirror"); |
||||||
|
let mut txn = doc.transact_mut(); |
||||||
|
let update = yrs::Update::decode_v1(&state) |
||||||
|
.map_err(|e| VerifierError::YrsError(e.to_string()))?; |
||||||
|
txn.apply_update(update); |
||||||
|
serde_json::json!({"xml":xml.get_string(&txn)}) |
||||||
|
} |
||||||
|
_ => return Err(VerifierError::InvalidBranch), |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
let store = self.graph_dataset.as_ref().unwrap(); |
||||||
|
let parsed = Query::parse("CONSTRUCT { ?s ?p ?o } WHERE { ?s ?p ?o }", None).unwrap(); |
||||||
|
let results = store |
||||||
|
.query(parsed, self.resolve_target_for_sparql(target, true)?) |
||||||
|
.map_err(|e| VerifierError::OxigraphError(e.to_string()))?; |
||||||
|
let results = if let QueryResults::Graph(quads) = results { |
||||||
|
let mut results = Vec::with_capacity(quads.size_hint().0); |
||||||
|
for quad in quads { |
||||||
|
match quad { |
||||||
|
Err(e) => return Err(VerifierError::OxigraphError(e.to_string())), |
||||||
|
Ok(triple) => results.push(triple.to_string()), |
||||||
|
} |
||||||
|
} |
||||||
|
results |
||||||
|
} else { |
||||||
|
return Err(VerifierError::OxigraphError( |
||||||
|
"Invalid Oxigraph query result".to_string(), |
||||||
|
)); |
||||||
|
}; |
||||||
|
|
||||||
|
let res = json!({ |
||||||
|
"discrete": discrete, |
||||||
|
"graph": results, |
||||||
|
}); |
||||||
|
|
||||||
|
Ok(serde_json::to_string(&res).unwrap()) |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue