add contact with QRCode

master
Niko PLP 24 hours ago
parent ef93afe4cb
commit 6fe3624ba0
  1. 3
      Cargo.lock
  2. 2
      helpers/app-auth/src/App.svelte
  3. 2
      helpers/net-auth/src/App.svelte
  4. 3
      nextgraph/src/local_broker.rs
  5. 5
      ng-app/index-web.html
  6. 2
      ng-app/src/App.svelte
  7. 116
      ng-app/src/apps/ContactEditor.svelte
  8. 88
      ng-app/src/apps/ProfileEditor.svelte
  9. 84
      ng-app/src/apps/ProfileQrCode.svelte
  10. 88
      ng-app/src/apps/ProfileView.svelte
  11. 42
      ng-app/src/apps/SocialQueryDemo.svelte
  12. 17
      ng-app/src/classes.ts
  13. 2
      ng-app/src/lib/Login.svelte
  14. 2
      ng-app/src/lib/icons/DataClassIcon.svelte
  15. 7
      ng-app/src/lib/icons/ZeraIcon.svelte
  16. 5
      ng-app/src/locales/en.json
  17. 2
      ng-app/src/routes/WalletInfo.svelte
  18. 4
      ng-app/src/routes/WalletLoginQr.svelte
  19. 4
      ng-app/src/store.ts
  20. 7
      ng-app/src/tab.ts
  21. 40
      ng-app/src/zeras.ts
  22. 1
      ng-net/Cargo.toml
  23. 22
      ng-net/src/app_protocol.rs
  24. 45
      ng-net/src/types.rs
  25. 5
      ng-oxigraph/src/oxigraph/storage/mod.rs
  26. 6
      ng-repo/src/errors.rs
  27. 8
      ng-repo/src/store.rs
  28. 7
      ng-repo/src/types.rs
  29. 64
      ng-sdk-js/src/lib.rs
  30. 2
      ng-verifier/Cargo.toml
  31. 1
      ng-verifier/src/commits/transaction.rs
  32. 8
      ng-verifier/src/inbox_processor.rs
  33. 244
      ng-verifier/src/request_processor.rs
  34. 82
      ng-verifier/src/verifier.rs
  35. 33
      ng-wallet/src/types.rs

3
Cargo.lock generated

@ -3376,6 +3376,7 @@ dependencies = [
"unique_id",
"url",
"web-time",
"zeroize",
]
[[package]]
@ -3532,6 +3533,7 @@ dependencies = [
"async-std",
"async-trait",
"automerge",
"base64-url",
"either",
"futures",
"getrandom 0.2.10",
@ -3540,6 +3542,7 @@ dependencies = [
"ng-oxigraph",
"ng-repo",
"ng-storage-rocksdb",
"qrcode",
"rand 0.7.3",
"sbbf-rs-safe",
"serde",

@ -56,7 +56,7 @@
routes.set("/user", User);
routes.set("/wallet", WalletInfo);
routes.set("/user/accounts", AccountInfo);
routes.set("/wallet/scanqr", ScanQRWeb);
routes.set("/scanqr", ScanQRWeb);
routes.set("*", NotFound);
let unsubscribe = () => {};

@ -48,7 +48,7 @@
// routes.set("/user/registered", UserRegistered);
// routes.set("/wallet", WalletInfo);
// routes.set("/user/accounts", AccountInfo);
// routes.set("/wallet/scanqr", ScanQRWeb);
// routes.set("/scanqr", ScanQRWeb);
// routes.set("/install", Install);
routes.set("*", NotFound);

@ -1683,8 +1683,9 @@ pub fn wallet_to_wallet_recovery(
Wallet::V0(v0) => {
let mut content = v0.content.clone();
content.security_img = vec![];
content.security_txt = String::new();
NgQRCodeWalletRecoveryV0 {
wallet: content,
wallet: serde_bare::to_vec(&content).unwrap(),
pazzle,
mnemonic,
pin,

@ -101,8 +101,9 @@
<div class="noshow" id="app-loading">&nbsp;&nbsp;&nbsp;Loading ...</div>
<div id="error-no-wasm" class="error-no-wasm-hidden">
Your browser is too old and does not support NextGraph. <br/>Please upgrade to a newer version of this browser,<br/> try with another browser,<br/> <br/>or <a href="https://nextgraph.org/download">install our native apps on <br/>
Linux, macOS, Windows desktops and laptops,<br/> and iOS, Android mobiles.</a><br/><br/>If you are using jshelter or another javascript protection mechanism, please deactivate it as we need access to the WebWorker facility of your browser.
Your browser is too old or is miss-configured. <br/>Please upgrade to a newer version of this browser,<br/> try with another browser,<br/> <br/>or <a href="https://nextgraph.org/download">install our native apps on <br/>
Linux, macOS, Windows desktops and laptops,<br/> and iOS, Android mobiles.</a><br/><br/>If you are using jshelter or another javascript protection mechanism,
please deactivate it as we need access to the WebWorker, JIT and WASM features of your browser. If those features are disabled, please enable them for this website.
</div>
<noscript style="display:grid;">
NextGraph cannot load as Javascript is deactivated.<br/>

@ -59,7 +59,7 @@
routes.set("/user/registered", UserRegistered);
routes.set("/wallet", WalletInfo);
routes.set("/user/accounts", AccountInfo);
routes.set("/wallet/scanqr", ScanQR);
routes.set("/scanqr", ScanQR);
if (import.meta.env.NG_APP_WEB) routes.set("/install", Install);
routes.set("/shared", Shared);
routes.set("/site", Site);

@ -0,0 +1,116 @@
<!--
// Copyright (c) 2022-2025 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 ng from "../api";
import { link } from "svelte-spa-router";
import { Button, Progressbar, Spinner, Alert } from "flowbite-svelte";
import{ PlusCircle } from "svelte-heros-v2";
import { push } from "svelte-spa-router";
import{ QrCode } from "svelte-heros-v2";
import { t } from "svelte-i18n";
import {
in_memory_discrete, open_viewer, set_viewer, set_editor, set_view_or_edit, cur_tab_branch_class, cur_tab_doc_can_edit, cur_tab
} from "../tab";
import DataClassIcon from "../lib/icons/DataClassIcon.svelte";
import {
openModalCreate,
sparql_query,
active_session,
scanned_qr_code,
check_has_camera,
toast_error,
display_error,
} from "../store";
import { onDestroy, onMount, tick } from "svelte";
export let commits;
const open_scanner = () => {
push("#/scanqr");
};
let container: HTMLElement;
let has_camera = false;
let has_name = undefined;
let has_email = undefined;
async function scrollToTop() {
await tick();
if (container) container.scrollIntoView();
}
onMount(async () => {
if (!$active_session) {
push("#/");
return;
}
has_camera = await check_has_camera();
console.log("has_camera",has_camera)
if ($scanned_qr_code) {
on_qr_scanned($scanned_qr_code);
scanned_qr_code.set("");
}
await scrollToTop();
});
$: if (commits) { contained(commits.graph) }
async function on_qr_scanned(text: string) {
try {
console.log("got QR",text, "did:ng:"+$cur_tab.doc.nuri);
await ng.import_contact_from_qrcode($active_session.session_id, "did:ng:"+$cur_tab.doc.nuri, text);
} catch (e) {
console.error(e)
toast_error(display_error(e));
}
}
function contained(graph) {
let ret = [];
for (const g of graph) {
if (g.substring(57,91) === "http://www.w3.org/2006/vcard/ns#fn") {
has_name = g.substring(94, g.length-1);
} else if (g.substring(57,97) === "http://www.w3.org/2006/vcard/ns#hasEmail") {
has_email = g.substring(100, g.length-1);
}
}
ret.sort((a, b) => a.hash.localeCompare(b.hash));
return ret;
}
async function test() {
if (!has_camera) {
await on_qr_scanned("AgBPtkD9jg11uDj7FTK0VqWb_aVxYvoyjFyIWs5VwCOICwAAABsxv_FXViA-5LUMNjARLJCiS3nOc7WYdoVQYgWn2ukcB25vIG5hbWUBDmZha2VAZW1haWwuY29t");
}
}
</script>
<div class="flex-col p-5" bind:this={container}>
{#if !has_camera && !has_name}
<Alert class="m-2" color="red">No camera available. You cannot import with QR-code</Alert>
{/if}
{#if !has_name}
<Button
on:click={test}
on:keypress={open_scanner}
class="select-none ml-2 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"
>
<QrCode tabindex="-1" class="mr-2 focus:outline-none" />
Import with QR-code
</Button><br/>
{/if}
Name: {has_name || ""}<br/>
{#if has_email}
Email: {has_email}<br/>
{/if}
</div>

@ -0,0 +1,88 @@
<!--
// Copyright (c) 2022-2025 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 ng from "../api";
import { link } from "svelte-spa-router";
import { Button, Progressbar, Spinner, Alert } from "flowbite-svelte";
import{ PlusCircle } from "svelte-heros-v2";
import { t } from "svelte-i18n";
import {
in_memory_discrete, open_viewer, set_viewer, set_editor, set_view_or_edit, cur_tab_branch_class, cur_tab_doc_can_edit, cur_tab
} from "../tab";
import DataClassIcon from "../lib/icons/DataClassIcon.svelte";
import {
openModalCreate,
sparql_query,
active_session
} from "../store";
import {
Clipboard
} from "svelte-heros-v2";
export let commits;
function contained(graph) {
let ret = [];
for (const g of graph) {
if (g.substring(57,90) === "http://www.w3.org/ns/ldp#contains") {
let nuri = g.substring(93,146);
let repo = nuri;
nuri = nuri + ":" + $cur_tab.store.overlay;
let hash = nuri.substring(9,16);
ret.push({nuri,hash,repo});
}
}
ret.sort((a, b) => a.hash.localeCompare(b.hash));
return ret;
}
async function fetch_header(repo) {
try {
let res = await ng.fetch_header($active_session.session_id, repo);
return res;
}catch(e){
console.error(e);
return {};
}
}
const create = () => {
openModalCreate();
}
const config = {
class: "mr-2 w-6 h-6 shrink-0 focus:outline-none"
}
</script>
<div class="flex-col p-5">
{#each contained(commits.graph) as doc}
{#await fetch_header(doc.repo)}
<div class="flex"> <Clipboard tabindex="-1" class="mr-2 w-6 h-6 shrink-0 focus:outline-none"/><div class="flex font-mono mb-3"> <a use:link href="/{doc.nuri}">{doc.hash}</a> </div> </div>
{:then header}
<div class="flex" title="{header.about || ''}"> {#if header.class}<DataClassIcon {config} dataClass={header.class}/>{:else}<Clipboard tabindex="-1" class="mr-2 w-6 h-6 shrink-0 focus:outline-none"/>{/if}<div class="flex font-mono mb-3"> <a use:link href="/{doc.nuri}">{header.title || doc.hash}</a> </div></div>
{/await}
{/each}
{#if commits.graph.length == 0 || contained(commits.graph).length == 0}
<p>{$t("doc.empty_container")}</p>
{#if $cur_tab_doc_can_edit}
<button
on:click={create}
on:keypress={create}
class="select-none ml-0 mt-2 mb-10 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"
>
<PlusCircle tabindex="-1" class="mr-2 focus:outline-none" />
{$t("doc.create")}
</button>
{/if}
{/if}
</div>

@ -0,0 +1,84 @@
<!--
// Copyright (c) 2022-2025 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 ng from "../api";
import { link, push } from "svelte-spa-router";
import { onDestroy, onMount, tick } from "svelte";
import { Button, Progressbar, Spinner, Alert } from "flowbite-svelte";
import{ PlusCircle, ArrowLeft } from "svelte-heros-v2";
import { t } from "svelte-i18n";
import {
in_memory_discrete, open_viewer, set_viewer, set_editor, set_view_or_edit, cur_tab_branch_class, cur_tab_doc_can_edit, cur_tab
} from "../tab";
import {
openModalCreate,
sparql_query,
active_session
} from "../store";
export let commits;
let container: HTMLElement;
let generation_state: "before_start" | "loading" | "generated" =
"before_start";
let generated_qr: string | undefined = undefined;
async function scrollToTop() {
await tick();
container.scrollIntoView();
}
onMount(async () => {
if (!$active_session) {
push("#/");
return;
}
await scrollToTop();
await generate_qr_code();
});
async function generate_qr_code() {
generation_state = "loading";
console.log(container.clientWidth);
generated_qr = await ng.get_qrcode_for_profile(
$active_session.session_id,
$cur_tab.store.store_type == "public", // are we public or protected?
Math.min(container.clientWidth, 800)
);
generation_state = "generated";
}
function back_to_profile_viewer() {
set_viewer("n:g:z:profile");
}
</script>
<div class="flex-col" bind:this={container}>
{#if generation_state == "generated"}
<div class="mx-auto">
{@html generated_qr}
</div>
<button
on:click={back_to_profile_viewer}
on:keypress={back_to_profile_viewer}
class="select-none mx-6 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"
><ArrowLeft
tabindex="-1"
class="w-8 h-8 mr-2 -ml-1 transition duration-75 focus:outline-none group-hover:text-gray-900 dark:group-hover:text-white"
/>{$t("buttons.back")}</button
>
{/if}
</div>

@ -0,0 +1,88 @@
<!--
// Copyright (c) 2022-2025 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 ng from "../api";
import { link } from "svelte-spa-router";
import { Button, Progressbar, Spinner, Alert } from "flowbite-svelte";
import{ PlusCircle } from "svelte-heros-v2";
import { t } from "svelte-i18n";
import {
in_memory_discrete, open_viewer, set_viewer, set_editor, set_view_or_edit, cur_tab_branch_class, cur_tab_doc_can_edit, cur_tab
} from "../tab";
import DataClassIcon from "../lib/icons/DataClassIcon.svelte";
import {
openModalCreate,
sparql_query,
active_session
} from "../store";
import {
Clipboard
} from "svelte-heros-v2";
export let commits;
function contained(graph) {
let ret = [];
for (const g of graph) {
if (g.substring(57,90) === "http://www.w3.org/ns/ldp#contains") {
let nuri = g.substring(93,146);
let repo = nuri;
nuri = nuri + ":" + $cur_tab.store.overlay;
let hash = nuri.substring(9,16);
ret.push({nuri,hash,repo});
}
}
ret.sort((a, b) => a.hash.localeCompare(b.hash));
return ret;
}
async function fetch_header(repo) {
try {
let res = await ng.fetch_header($active_session.session_id, repo);
return res;
}catch(e){
console.error(e);
return {};
}
}
const create = () => {
openModalCreate();
}
const config = {
class: "mr-2 w-6 h-6 shrink-0 focus:outline-none"
}
</script>
<div class="flex-col p-5">
{#each contained(commits.graph) as doc}
{#await fetch_header(doc.repo)}
<div class="flex"> <Clipboard tabindex="-1" class="mr-2 w-6 h-6 shrink-0 focus:outline-none"/><div class="flex font-mono mb-3"> <a use:link href="/{doc.nuri}">{doc.hash}</a> </div> </div>
{:then header}
<div class="flex" title="{header.about || ''}"> {#if header.class}<DataClassIcon {config} dataClass={header.class}/>{:else}<Clipboard tabindex="-1" class="mr-2 w-6 h-6 shrink-0 focus:outline-none"/>{/if}<div class="flex font-mono mb-3"> <a use:link href="/{doc.nuri}">{header.title || doc.hash}</a> </div></div>
{/await}
{/each}
{#if commits.graph.length == 0 || contained(commits.graph).length == 0}
<p>{$t("doc.empty_container")}</p>
{#if $cur_tab_doc_can_edit}
<button
on:click={create}
on:keypress={create}
class="select-none ml-0 mt-2 mb-10 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"
>
<PlusCircle tabindex="-1" class="mr-2 focus:outline-none" />
{$t("doc.create")}
</button>
{/if}
{/if}
</div>

@ -15,7 +15,9 @@
sparql_update,
toast_error,
toast_success,
active_session
active_session,
display_error,
online
} from "../store";
import ng from "../api";
import {
@ -42,19 +44,25 @@
const openQuery = async () => {
await sparql_update("INSERT DATA { <> <did:ng:x:ng#social_query_sparql> \"CONSTRUCT { ?s ?p ?o } WHERE { ?s ?p ?o }\".}");
let commit_id = commits.heads[0];
let commit_key = commits.head_keys[0];
let session = $active_session;
if (!session) return;
let request_nuri = "did:ng:"+$cur_tab.doc.nuri+":c:"+commit_id+":k:"+commit_key;
await ng.social_query_start(
session.session_id,
"did:ng:a",
request_nuri,
"did:ng:d:c",
2,
);
//TODO : return now if already processing (when LDO for svelte is ready)
// and even disable the button in that case
try {
await sparql_update("INSERT DATA { <> <did:ng:x:ng#social_query_sparql> \"CONSTRUCT { ?s ?p ?o } WHERE { ?s ?p ?o }\".}");
let commit_id = commits.heads[0];
let commit_key = commits.head_keys[0];
let session = $active_session;
if (!session) return;
let request_nuri = "did:ng:"+$cur_tab.doc.nuri+":c:"+commit_id+":k:"+commit_key;
await ng.social_query_start(
session.session_id,
"did:ng:a",
request_nuri,
"did:ng:d:c",
2,
);
} catch (e) {
toast_error(display_error(e));
}
}
onMount(()=>{
@ -62,7 +70,6 @@
});
const info = () => {
}
</script>
@ -78,14 +85,15 @@
>
info
</button>
<button
<Button
on:click={openQuery}
on:keypress={openQuery}
disabled={!$online}
class="select-none ml-2 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"
>
<Lifebuoy tabindex="-1" class="mr-2 focus:outline-none" />
Start query
</button>
</Button>
{#if source}
<Highlight {language} code={source} class="mb-10" let:highlighted >

@ -728,9 +728,12 @@ export const official_classes = {
"ng:crdt": "Graph",
"ng:n": "Contact",
"ng:a": "Contact: an Individual, Organization or Group",
"ng:o": "n:g:z:contact_editor",
"implemented": true,
"ng:x": {
"vcard":true,
"foaf": true,
"schema": true,
},
"ng:include": ["data:graph"],
"ng:compat": ["foaf:Person","foaf:Agent","vcard:Individual", "vcard:Organization", "vcard:Group", "file:iana:text:vcard", "file:iana:application:vcard+json", "file:iana:application:vcard+xml" ],
@ -784,6 +787,20 @@ export const official_classes = {
"ng:n": "Live",
"ng:a": "A live session of video or audio, with optional chat",
},
"social:profile": {
"ng:crdt": "Graph",
"ng:n": "Social Profile",
"ng:a": "The profile your share to others",
"ng:o": "n:g:z:profile",
"ng:w": "n:g:z:profile_editor",
"ng:x": {
"rdf": true,
"rdfs": true,
"ldp": true,
},
"ng:include": "data:container",
"implemented": true
},
"social:profile:skills:programming": {
"ng:crdt": "Graph",
"ng:n": "Skills Profile",

@ -430,7 +430,7 @@
<Alert color="orange" class="">
Access to local storage is denied. <br/>You won't be able to save your wallet in this browser.<br/>
If you wanted to save it, please allow storing local data<br/> for the websites {location.origin} <br/>
and https://nextgraph.net and then reload the page.
and https://nextgraph.net and then reload the page. <br/> You might need to all third-party cookies too.
</Alert>
</div>
{:else}

@ -44,6 +44,7 @@
CursorArrowRays,
Megaphone,
User,
UserCircle,
Clock,
CalendarDays,
Calendar,
@ -132,6 +133,7 @@
"social:channel": Megaphone,
"social:stream": Bolt,
"social:contact": User,
"social:profile": UserCircle,
"social:event": Clock,
"social:calendar": CalendarDays,
"social:scheduler": Calendar,

@ -43,7 +43,7 @@
Ticket,
CursorArrowRays,
Megaphone,
User,
UserCircle,
Clock,
CalendarDays,
Calendar,
@ -73,6 +73,8 @@
Square3Stack3d,
QueueList,
Lifebuoy,
QrCode,
User,
} from "svelte-heros-v2";
import JsonIcon from "./JsonIcon.svelte";
import JsonLdIcon from "./JsonLdIcon.svelte";
@ -115,6 +117,9 @@
grid: Squares2x2,
view: Eye,
social_query: Lifebuoy,
profile: UserCircle,
qrcode: QrCode,
contact: User,
};
const prefix_mapping = {};

@ -655,9 +655,12 @@
"WsError": "WebSocket error",
"cannot_load_this_file": "Cannot load this file",
"graph_not_found": "Graph not found",
"SocialQueryAlreadyStarted": "Social Query already started",
"ContactAlreadyExists": "Contact already added to your account",
"ContactNotFound": "You don't have any contact. We cannot start the Social Query",
"no_wasm_on_old_safari": "Your Safari browser is too old (version before 14.1). As a result we cannot load Automerge, needed for this document. Please upgrade your macOS or iOS system",
"BrowserTooOld": "Your browser is too old. Please upgrade it, use another browser, or install our native app. If you are using jshelter or another javascript protection mechanism, please deactivate it as we need access to the WebWorker facility of your browser.",
"NoLocalStorage": "You have disabled local storage in your browser. Please allow the current website (and https://nextgraph.net website) to store data in this browser as otherwise we cannot proceed with Wallet creation. After allowing storage, please refresh the current page."
"NoLocalStorage": "You have disabled local storage in your browser. Please allow the current website (and https://nextgraph.net website) to store data in this browser as otherwise we cannot proceed with Wallet creation. After allowing storage, please refresh the current page. You might need to all third-party cookies too."
},
"connectivity": {
"stopped": "Stopped",

@ -117,7 +117,7 @@
}
async function open_scanner() {
push("#/wallet/scanqr");
push("#/scanqr");
}
async function generate_text_code() {

@ -24,7 +24,7 @@
import { wallet_from_import, scanned_qr_code, display_error, check_has_camera } from "../store";
import ng from "../api";
// <a href="/wallet/scanqr" use:link>
// <a href="/scanqr" use:link>
let top: HTMLElement;
@ -44,7 +44,7 @@
let rendezvous_code;
const open_scanner = () => {
push("#/wallet/scanqr");
push("#/scanqr");
};

@ -220,9 +220,11 @@ export const check_has_camera = async () => {
else {
try {
const devices = await navigator.mediaDevices.enumerateDevices();
console.log(devices);
has_camera =
devices.filter((device) => device.kind === "videoinput").length > 0;
} catch {
} catch(e) {
console.log(e);
has_camera = false;
}
}

@ -129,6 +129,13 @@ const class_to_viewers_editors = (class_name: string) => {
if (!has_discrete) {
if (class_def["ng:o"]) graph_viewers.push(class_def["ng:o"]);
if (class_def["ng:w"]) graph_editors.push(class_def["ng:w"]);
for (const additional_v of find_viewers_for_class(class_name)){
if (!graph_viewers.includes(additional_v)) graph_viewers.push(additional_v);
}
for (const additional_e of find_editors_for_class(class_name)){
if (!graph_editors.includes(additional_e)) graph_editors.push(additional_e);
}
}
for (const additional_g_v of find_viewers_for_class("data:graph")){
if (!graph_viewers.includes(additional_g_v)) graph_viewers.push(additional_g_v);

@ -519,6 +519,46 @@ export const official_apps = {
"ng:o": ["data:collection","data:container"],
"ng:w": ["data:collection","data:container"],
},
"n:g:z:profile": {
"ng:n": "Profile",
"ng:a": "See the profile of a user of group",
"ng:c": "app",
"ng:u": "profile",//favicon. can be a did:ng:j
"ng:g": "n:g:z:profile",
"ng:b": "ProfileView",
implemented: true,
"ng:o": ["social:profile"],
},
"n:g:z:profile_qrcode": {
"ng:n": "QR Code",
"ng:a": "Share your profile with others via QR Code",
"ng:c": "app",
"ng:u": "qrcode",//favicon. can be a did:ng:j
"ng:g": "n:g:z:profile_qrcode",
"ng:b": "ProfileQrCode",
implemented: true,
"ng:o": ["social:profile"],
},
"n:g:z:profile_editor": {
"ng:n": "Profile Editor",
"ng:a": "Edit your profile",
"ng:c": "app",
"ng:u": "profile",//favicon. can be a did:ng:j
"ng:g": "n:g:z:profile_editor",
"ng:b": "ProfileEditor",
implemented: true,
"ng:w": ["social:profile"],
},
"n:g:z:contact_editor": {
"ng:n": "Contact",
"ng:a": "Add, View, and Edit a Contact",
"ng:c": "app",
"ng:u": "contact",//favicon. can be a did:ng:j
"ng:g": "n:g:z:contact_editor",
"ng:b": "ContactEditor",
implemented: true,
"ng:o": ["social:contact"],
},
"n:g:z:grid": {
"ng:n": "Grid",
"ng:a": "See the content of document as a grid",

@ -38,6 +38,7 @@ regex = "1.8.4"
base64-url = "2.0.0"
web-time = "0.2.0"
time = "0.3.41"
zeroize = { version = "1.7.0", features = ["zeroize_derive"] }
ng-repo = { path = "../ng-repo", version = "0.1.1-alpha.2" }
reqwest = { version = "0.11.18", features = ["json","native-tls-vendored"] }

@ -555,6 +555,15 @@ impl NuriV0 {
Err(NgError::InvalidNuri)
}
pub fn new_from_repo_nuri(from: &String) -> Result<Self, NgError> {
let repo_id = Self::from_repo_nuri_to_id(from)?;
let mut n = Self::new_empty();
n.target = NuriTargetV0::Repo(repo_id);
return Ok(n);
}
pub fn new_from_commit(from: &String) -> Result<Self, NgError> {
let c = RE_COMMIT.captures(&from);
@ -695,6 +704,8 @@ pub enum AppRequestCommandV0 {
InboxPost,
SocialQueryStart,
SocialQueryCancel,
QrCodeProfile,
QrCodeProfileImport,
}
impl AppRequestCommandV0 {
@ -734,6 +745,12 @@ impl AppRequestCommandV0 {
pub fn new_header() -> Self {
AppRequestCommandV0::Header
}
pub fn new_qrcode_for_profile() -> Self {
AppRequestCommandV0::QrCodeProfile
}
pub fn new_qrcode_profile_import() -> Self {
AppRequestCommandV0::QrCodeProfileImport
}
pub fn new_fetch_header() -> Self {
AppRequestCommandV0::Fetch(AppFetchContentV0::Header)
}
@ -1015,6 +1032,8 @@ pub enum AppRequestPayloadV0 {
},
//RemoveFile
//Invoke(InvokeArguments),
QrCodeProfile(u32),
QrCodeProfileImport(String),
}
#[derive(Clone, Debug, Serialize, Deserialize)]
@ -1281,4 +1300,7 @@ impl AppResponse {
pub fn commits(commits: Vec<String>) -> Self {
AppResponse::V0(AppResponseV0::Commits(commits))
}
pub fn text(text: String) -> Self {
AppResponse::V0(AppResponseV0::Text(text))
}
}

@ -21,6 +21,7 @@ use std::{
use serde::{Deserialize, Serialize};
use web_time::SystemTime;
use zeroize::{Zeroize, ZeroizeOnDrop};
use ng_repo::errors::*;
use ng_repo::log::*;
@ -5175,6 +5176,50 @@ pub enum NgLink {
V0(NgLinkV0),
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct NgQRCodeWalletTransferV0 {
pub broker: BrokerServerV0,
pub rendezvous: SymKey, // Rendez-vous ID
pub secret_key: SymKey,
pub is_rendezvous: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct NgQRCodeProfileSharingV0 {
pub inbox: PubKey,
pub profile: StoreRepo,
pub name: String,
pub email: Option<String>,
}
#[derive(Clone, Debug, Zeroize, ZeroizeOnDrop, Serialize, Deserialize)]
pub struct NgQRCodeWalletRecoveryV0 {
#[zeroize(skip)]
#[serde(with = "serde_bytes")]
pub wallet: Vec<u8>, // a serialized WalletContentV0, //of which security_img and security_text are emptied
pub pazzle: Vec<u8>,
pub mnemonic: [u16; 12],
pub pin: [u8; 4],
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum NgQRCode {
WalletTransferV0(NgQRCodeWalletTransferV0),
WalletRecoveryV0(NgQRCodeWalletRecoveryV0),
ProfileSharingV0(NgQRCodeProfileSharingV0),
}
impl NgQRCode {
pub fn from_code(code: String) -> Result<Self, NgError> {
let decoded = base64_url::decode(&code).map_err(|_| NgError::SerializationError)?;
Ok(serde_bare::from_slice(&decoded)?)
}
pub fn to_code(&self) -> String {
let ser = serde_bare::to_vec(self).unwrap();
base64_url::encode(&ser)
}
}
// TODO: PermaLinks and InboxPost (and ExtRequests)
#[cfg(test)]

@ -628,7 +628,6 @@ impl NgCommitQuadIterator {
self.current_is_added = false;
self.current_add_is_removed = None;
self.current = None;
ret
}
@ -689,6 +688,7 @@ impl NgCommitQuadIterator {
return Err(false);
}
} else {
self.iter.next();
return Err(false);
}
}
@ -707,6 +707,9 @@ impl Iterator for NgCommitQuadIterator {
if let Ok(found) = res {
return found;
}
// if res.err().unwrap() {
// return None;
// }
}
}
}

@ -95,6 +95,7 @@ pub enum NgError {
BrokerNotFound,
SparqlError(String),
ContactNotFound,
SocialQueryAlreadyStarted,
}
impl Error for NgError {}
@ -191,6 +192,7 @@ impl From<VerifierError> for NgError {
VerifierError::StoreNotFound => NgError::StoreNotFound,
VerifierError::BranchNotFound => NgError::BranchNotFound,
VerifierError::SparqlError(s) => NgError::SparqlError(s),
VerifierError::InternalError => NgError::InternalError,
_ => NgError::VerifierError(e),
}
}
@ -390,6 +392,10 @@ pub enum VerifierError {
InvalidResponse,
SparqlError(String),
InboxError(String),
QrCode(String),
InvalidProfile,
ContactAlreadyExists,
InternalError,
}
impl Error for VerifierError {}

@ -293,7 +293,13 @@ impl Store {
) -> Result<(Repo, Vec<(Commit, Vec<Digest>)>), NgError> {
let is_store = branch_crdt.is_none();
if is_store {
branch_crdt = Some(BranchCrdt::Graph("data:container".to_string()));
branch_crdt = Some(BranchCrdt::Graph(
if is_private_store {
"data:container"
} else {
"social:profile"
}.to_string()
));
}
let mut events = Vec::with_capacity(9);

@ -888,6 +888,13 @@ impl StoreRepo {
}
}
pub fn is_public(&self) -> bool {
match self {
Self::V0(StoreRepoV0::PublicStore(_)) => true,
_ => false,
}
}
// pub fn overlay_id_for_storage_purpose(
// &self,
// store_overlay_branch_readcap_secret: Option<ReadCapSecret>,

@ -1053,6 +1053,70 @@ pub async fn wallet_import(
Ok(serde_wasm_bindgen::to_value(&client).unwrap())
}
#[wasm_bindgen]
pub async fn import_contact_from_qrcode(
session_id: JsValue,
doc_nuri: String,
qrcode: String,
) -> Result<(), String> {
let session_id: u64 = serde_wasm_bindgen::from_value::<u64>(session_id)
.map_err(|_| "Deserialization error of session_id".to_string())?;
let mut request = AppRequest::new(
AppRequestCommandV0::QrCodeProfileImport,
NuriV0::new_from_repo_nuri(&doc_nuri).map_err(|e| e.to_string())?,
Some(AppRequestPayload::V0(AppRequestPayloadV0::QrCodeProfileImport(qrcode))),
);
request.set_session_id(session_id);
let response = nextgraph::local_broker::app_request(request)
.await
.map_err(|e: NgError| e.to_string())?;
match response {
AppResponse::V0(AppResponseV0::Ok) => Ok(()),
AppResponse::V0(AppResponseV0::Error(e)) => Err(e),
_ => Err("invalid response".to_string()),
}
}
#[wasm_bindgen]
pub async fn get_qrcode_for_profile(
session_id: JsValue,
public: bool,
size: JsValue,
) -> Result<String, String> {
let session_id: u64 = serde_wasm_bindgen::from_value::<u64>(session_id)
.map_err(|_| "Deserialization error of session_id".to_string())?;
let size: u32 = serde_wasm_bindgen::from_value::<u32>(size)
.map_err(|_| "Deserialization error of size".to_string())?;
let nuri = if public {
NuriV0::new_public_store_target()
} else {
NuriV0::new_protected_store_target()
};
let mut request = AppRequest::new(
AppRequestCommandV0::QrCodeProfile,
nuri,
Some(AppRequestPayload::V0(AppRequestPayloadV0::QrCodeProfile(size))),
);
request.set_session_id(session_id);
let response = nextgraph::local_broker::app_request(request)
.await
.map_err(|e: NgError| e.to_string())?;
match response {
AppResponse::V0(AppResponseV0::Text(qrcode)) => Ok(qrcode),
AppResponse::V0(AppResponseV0::Error(e)) => Err(e),
_ => Err("invalid response".to_string()),
}
}
#[cfg(wasmpack_target = "nodejs")]
#[wasm_bindgen(module = "/js/node.js")]
extern "C" {

@ -29,9 +29,11 @@ either = "1.8.1"
futures = "0.3.24"
lazy_static = "1.4.0"
async-trait = "0.1.64"
base64-url = "2.0.0"
async-std = { version = "1.12.0", features = [ "attributes", "unstable" ] }
automerge = "0.5.11"
yrs = "0.19.2"
qrcode = { version = "0.14.1", default-features = false, features = ["svg"] }
sbbf-rs-safe = "0.3.2"
ng-repo = { path = "../ng-repo", version = "0.1.1-alpha.2" }
ng-net = { path = "../ng-net", version = "0.1.1-alpha.2" }

@ -551,7 +551,6 @@ impl Verifier {
let commit_name =
NuriV0::commit_graph_name(&update.commit_id, &update.overlay_id);
let commit_encoded = numeric_encoder::StrHash::new(&commit_name);
let cv_graphname = NamedNode::new_unchecked(commit_name);
let cv_graphname_ref = GraphNameRef::NamedNode((&cv_graphname).into());
let ov_main = if branch_is_main {

@ -369,7 +369,7 @@ impl Verifier {
if msg.body.from_inbox.is_none() {
// TODO log error
// we do nothing as this is invalid msg. it must have a from.
return Ok(())
return Err(VerifierError::InvalidSocialQuery)
}
// TODO: first we open the response.forwarder_id (because in webapp, it might not be loaded yet)
@ -544,12 +544,6 @@ impl Verifier {
let quads = triples.into_iter().map(|t| t.in_graph(graph_name.clone()) ).collect();
// let quad = Quad {
// subject: NamedNode::new_unchecked(&nuri).into(),
// predicate: NG_ONTOLOGY_CLASS_NAME.clone().into(),
// object: Literal::new_simple_literal(primary_class).into(),
// graph_name: NamedNode::new_unchecked(&header_branch_nuri).into(),
// };
let commits = self.prepare_sparql_update(quads, vec![], vec![]).await?;

@ -17,6 +17,8 @@ use futures::SinkExt;
use futures::StreamExt;
use ng_net::actor::SoS;
use ng_net::types::InboxPost;
use ng_net::types::NgQRCode;
use ng_net::types::NgQRCodeProfileSharingV0;
use ng_oxigraph::oxigraph::sparql::EvaluationError;
use ng_oxigraph::oxigraph::sparql::{results::*, Query, QueryResults};
use ng_oxigraph::oxrdf::{Literal, NamedNode, Quad, Term};
@ -151,6 +153,69 @@ impl Verifier {
}
}
async fn update_header(&mut self, target: &NuriTargetV0, title: Option<String>, about: Option<String>) -> Result<(), VerifierError> {
let (repo_id, branch_id, store_repo) = self.resolve_header_branch(target)?;
let graph_name = NuriV0::branch_repo_graph_name(
&branch_id,
&repo_id,
&store_repo.overlay_id_for_storage_purpose(),
);
let base = NuriV0::repo_id(&repo_id);
let mut deletes = String::new();
let mut wheres = String::new();
let mut inserts = String::new();
if let Some(about) = about {
deletes += &format!("<> <{NG_ONTOLOGY_ABOUT}> ?a. ");
wheres += &format!("OPTIONAL {{ <> <{NG_ONTOLOGY_ABOUT}> ?a }} ");
if about.len() > 0 {
inserts += &format!(
"<> <{NG_ONTOLOGY_ABOUT}> \"{}\". ",
about.replace("\\", "\\\\").replace("\"", "\\\"")
);
}
}
if let Some(title) = title {
deletes += &format!("<> <{NG_ONTOLOGY_TITLE}> ?n. ");
wheres += &format!("OPTIONAL {{ <> <{NG_ONTOLOGY_TITLE}> ?n }} ");
if title.len() > 0 {
inserts += &format!(
"<> <{NG_ONTOLOGY_TITLE}> \"{}\". ",
title.replace("\\", "\\\\").replace("\"", "\\\"")
);
}
}
let query = format!(
"DELETE {{ {deletes} }} INSERT {{ {inserts} }} WHERE {{ {wheres} }}"
);
let oxistore = self.graph_dataset.as_ref().unwrap();
let update = ng_oxigraph::oxigraph::sparql::Update::parse(&query, Some(&base))
.map_err(|e| NgError::InternalError)?;
let res = oxistore.ng_update(update, Some(graph_name));
match res {
Err(e) => Err(VerifierError::InternalError),
Ok((inserts, removes)) => {
if inserts.is_empty() && removes.is_empty() {
Ok(())
} else {
self
.prepare_sparql_update(
Vec::from_iter(inserts),
Vec::from_iter(removes),
self.get_peer_id_for_skolem(),
)
.await?;
Ok(())
}
}
}
}
fn resolve_header_branch(
&self,
target: &NuriTargetV0,
@ -648,6 +713,48 @@ impl Verifier {
Ok(nuri_result)
}
async fn import_contact_from_qrcode(&mut self, repo_id: RepoId, contact: NgQRCodeProfileSharingV0) -> Result<(), VerifierError> {
let inbox_nuri_string: String = NuriV0::inbox(&contact.inbox);
let profile_nuri_string: String = NuriV0::from_store_repo_string(&contact.profile);
let a_or_b = if contact.profile.is_public() { "a" } else { "b" };
// checking if this contact has already been added
match self.sparql_query(
&NuriV0::new_entire_user_site(),
format!("ASK {{ ?s <did:ng:x:ng#d> <{inbox_nuri_string}> . ?s <did:ng:x:ng#{}> <{profile_nuri_string}> }}",
a_or_b ), None).await?
{
QueryResults::Boolean(true) => {
return Err(VerifierError::ContactAlreadyExists);
}
_ => {}
}
let contact_doc_nuri_string = NuriV0::repo_id(&repo_id);
let contact_doc_nuri = NuriV0::new_repo_target_from_id(&repo_id);
let has_email = contact.email.map_or("".to_string(), |email| format!("<> vcard:hasEmail \"{email}\"."));
let sparql_update = format!(" PREFIX ng: <did:ng:x:ng#>
PREFIX vcard: <http://www.w3.org/2006/vcard/ns#>
INSERT DATA {{ <> ng:{a_or_b} <{profile_nuri_string}>.
<> ng:d <{inbox_nuri_string}>.
<> a vcard:Individual .
<> vcard:fn \"{}\".
{has_email} }}", contact.name);
let ret = self
.process_sparql_update(&contact_doc_nuri, &sparql_update, &Some(contact_doc_nuri_string), vec![])
.await;
if let Err(e) = ret {
return Err(VerifierError::SparqlError(e));
}
self.update_header(&contact_doc_nuri.target, Some(contact.name), None).await?;
Ok(())
}
pub(crate) async fn process(
&mut self,
command: &AppRequestCommandV0,
@ -665,6 +772,24 @@ impl Verifier {
return Err(NgError::InvalidPayload);
};
let query_id = nuri.target.repo_id();
// checking that the query hasn't been started yet
match self.sparql_query(
&NuriV0::new_repo_target_from_id(query_id),
format!("ASK {{ <> <did:ng:x:ng#social_query_forwarder> ?forwarder }}"), Some(NuriV0::repo_id(query_id))).await?
{
QueryResults::Boolean(true) => {
return Err(NgError::SocialQueryAlreadyStarted);
}
_ => {}
}
// return error if not connected
if self.connected_broker.is_none() {
return Err(NgError::NotConnected);
}
// TODO: search for contacts (all stores, one store, a sparql query, etc..)
// (profile_nuri, inbox_nuri)
let contacts = if contacts_string.as_str() == "did:ng:d:c" {
@ -672,7 +797,7 @@ impl Verifier {
res.push(("did:ng:a:rjoQTS4LMBDcuh8CEjmTYrgALeApBg2cgKqyPEuQDUgA".to_string(),"did:ng:d:KMFdOcGjdFBQgA9QNEDWcgEErQ1isbvDe7d_xndNOUMA".to_string()));
res
} else {
unimplemented!();
return Ok(AppResponse::error(NgError::NotImplemented.to_string()));
};
// if no contact found, return here with an AppResponse::error
@ -694,7 +819,7 @@ impl Verifier {
let repo = self.repos.get(&from_profile_id).ok_or(NgError::RepoNotFound)?;
repo.store.clone()
};
let query_id = nuri.target.repo_id();
let definition_commit_body_ref = nuri.get_first_commit_ref()?;
let block_ids = Commit::collect_block_ids(definition_commit_body_ref.clone(), &store, true)?;
let mut blocks= Vec::with_capacity(block_ids.len());
@ -777,76 +902,55 @@ impl Verifier {
// Ok(SoS::Single(_)) => Ok(AppResponse::ok()),
// };
}
AppRequestCommandV0::QrCodeProfile => {
let size = if let Some(AppRequestPayload::V0(AppRequestPayloadV0::QrCodeProfile(size))) =
payload
{
size
} else {
return Err(NgError::InvalidPayload);
};
let public = match nuri.target {
NuriTargetV0::PublicProfile => true,
NuriTargetV0::ProtectedProfile => false,
_ => return Err(NgError::InvalidPayload)
};
return match self.get_qrcode_for_profile(public, size).await {
Err(e) => Ok(AppResponse::error(e.to_string())),
Ok(qrcode) => Ok(AppResponse::text(qrcode)),
};
}
AppRequestCommandV0::QrCodeProfileImport => {
let profile = if let Some(AppRequestPayload::V0(AppRequestPayloadV0::QrCodeProfileImport( text))) =
payload
{
let ser = base64_url::decode(&text).map_err(|_| NgError::SerializationError)?;
let code:NgQRCode = serde_bare::from_slice(&ser)?;
let profile = match code {
NgQRCode::ProfileSharingV0(profile) => profile,
_ => return Err(NgError::InvalidPayload)
};
profile
} else {
return Err(NgError::InvalidPayload);
};
let repo_id = match nuri.target {
NuriTargetV0::Repo(id) => id,
_ => return Err(NgError::InvalidPayload)
};
return match self.import_contact_from_qrcode(repo_id, profile).await {
Err(e) => Ok(AppResponse::error(e.to_string())),
Ok(()) => Ok(AppResponse::ok()),
};
}
AppRequestCommandV0::Header => {
if let Some(AppRequestPayload::V0(AppRequestPayloadV0::Header(doc_header))) =
payload
{
let (repo_id, branch_id, store_repo) =
match self.resolve_header_branch(&nuri.target) {
Err(e) => return Ok(AppResponse::error(e.to_string())),
Ok(a) => a,
};
let graph_name = NuriV0::branch_repo_graph_name(
&branch_id,
&repo_id,
&store_repo.overlay_id_for_storage_purpose(),
);
let base = NuriV0::repo_id(&repo_id);
let mut deletes = String::new();
let mut wheres = String::new();
let mut inserts = String::new();
if let Some(about) = doc_header.about {
deletes += &format!("<> <{NG_ONTOLOGY_ABOUT}> ?a. ");
wheres += &format!("OPTIONAL {{ <> <{NG_ONTOLOGY_ABOUT}> ?a }} ");
if about.len() > 0 {
inserts += &format!(
"<> <{NG_ONTOLOGY_ABOUT}> \"{}\". ",
about.replace("\\", "\\\\").replace("\"", "\\\"")
);
}
}
if let Some(title) = doc_header.title {
deletes += &format!("<> <{NG_ONTOLOGY_TITLE}> ?n. ");
wheres += &format!("OPTIONAL {{ <> <{NG_ONTOLOGY_TITLE}> ?n }} ");
if title.len() > 0 {
inserts += &format!(
"<> <{NG_ONTOLOGY_TITLE}> \"{}\". ",
title.replace("\\", "\\\\").replace("\"", "\\\"")
);
}
}
let query = format!(
"DELETE {{ {deletes} }} INSERT {{ {inserts} }} WHERE {{ {wheres} }}"
);
let oxistore = self.graph_dataset.as_ref().unwrap();
let update = ng_oxigraph::oxigraph::sparql::Update::parse(&query, Some(&base))
.map_err(|e| NgError::InternalError)?;
let res = oxistore.ng_update(update, Some(graph_name));
return Ok(match res {
Err(e) => AppResponse::error(NgError::InternalError.to_string()),
Ok((inserts, removes)) => {
if inserts.is_empty() && removes.is_empty() {
AppResponse::ok()
} else {
match self
.prepare_sparql_update(
Vec::from_iter(inserts),
Vec::from_iter(removes),
self.get_peer_id_for_skolem(),
)
.await
{
Err(e) => AppResponse::error(e.to_string()),
Ok(_) => AppResponse::ok(),
}
}
}
});
return match self.update_header(&nuri.target, doc_header.title, doc_header.about).await {
Ok(_) => Ok(AppResponse::ok()),
Err(e) => Ok(AppResponse::error(e.to_string()))
};
} else {
return Err(NgError::InvalidPayload);
}
@ -871,9 +975,7 @@ impl Verifier {
Err(e) => return Ok(AppResponse::error(e.to_string())),
Ok(a) => a,
};
self.open_branch(&repo_id, &branch_id, true).await?;
let graph_name = NuriV0::branch_repo_graph_name(
&branch_id,
&repo_id,
@ -1143,7 +1245,7 @@ impl Verifier {
},
},
_ => unimplemented!(),
_ => return Err(NgError::NotImplemented),
}
Ok(AppResponse::V0(AppResponseV0::Ok))
}

@ -29,6 +29,8 @@ use ng_oxigraph::oxigraph::sparql::Query;
use ng_oxigraph::oxigraph::sparql::QueryResults;
use ng_oxigraph::oxrdf::Term;
use ng_repo::utils::derive_key;
use qrcode::render::svg;
use qrcode::QrCode;
use sbbf_rs_safe::Filter;
use serde::{Deserialize, Serialize};
use web_time::SystemTime;
@ -285,11 +287,9 @@ impl Verifier {
let base = NuriV0::repo_id(&repo.id);
let parsed = Query::parse(&format!("SELECT ?title ?about WHERE {{ OPTIONAL {{ <> <{NG_ONTOLOGY_ABOUT}> ?about }} OPTIONAL {{ <> <{NG_ONTOLOGY_TITLE}> ?title }} }}"),
Some(&base)).map_err(|e| NgError::OxiGraphError(e.to_string()))?;
let results = oxistore
.query(parsed, Some(header_graph))
.map_err(|e| NgError::OxiGraphError(e.to_string()))?;
match results {
QueryResults::Solutions(mut sol) => {
if let Some(Ok(s)) = sol.next() {
@ -1566,6 +1566,84 @@ impl Verifier {
Ok(())
}
pub async fn get_qrcode_for_profile(&self, public: bool, size: u32) -> Result<String, VerifierError> {
let profile_id = if public {
self.public_store_id()
} else {
self.protected_store_id()
};
let repo = self.repos.get(&profile_id).ok_or(NgError::RepoNotFound)?;
let inbox = repo.inbox.to_owned().ok_or(NgError::InboxNotFound)?.to_pub();
let profile = repo.store.get_store_repo().clone();
let sparql = format!("
PREFIX vcard: <http://www.w3.org/2006/vcard/ns#>
SELECT ?name ?email WHERE
{{ <> vcard:fn ?name .
<> vcard:hasEmail ?email .
}}");
//log_info!("{sparql}");
let (mut name, mut email) = match self.sparql_query(
&NuriV0::new_repo_target_from_id(profile_id),
sparql, Some(NuriV0::repo_id(profile_id))).await?
{
QueryResults::Solutions(mut sols) => {
match sols.next() {
None => {
//log_info!("name or email not found");
(None, None)
}
Some(Err(e)) => {
return Err(VerifierError::SparqlError(e.to_string()));
}
Some(Ok(sol)) => {
let name = if let Some(Term::Literal(l)) = sol.get("name") {
Some(l.value().to_string())
} else {
None
};
let email = if let Some(Term::Literal(l)) = sol.get("email") {
Some(l.value().to_string())
} else {
None
};
(name, email)
}
}
}
_ => return Err(VerifierError::SparqlError(NgError::InvalidResponse.to_string())),
};
if name.is_none() {
//return Err(VerifierError::InvalidProfile);
name = Some("no name".to_string());
email = Some("fake@email.com".to_string());
}
let profile_sharing = NgQRCode::ProfileSharingV0(NgQRCodeProfileSharingV0 {
inbox,
profile,
name: name.unwrap(),
email
});
let ser = serde_bare::to_vec(&profile_sharing)?;
let encoded = base64_url::encode(&ser);
log_info!("qrcode= {encoded}");
match QrCode::with_error_correction_level(encoded.as_bytes(), qrcode::EcLevel::M) {
Ok(qr) => {
Ok(qr
.render()
.max_dimensions(size, size)
.dark_color(svg::Color("#000000"))
.light_color(svg::Color("#ffffff"))
.build()
)
}
Err(e) => Err(VerifierError::QrCode(e.to_string())),
}
}
pub async fn inbox(&mut self, msg: InboxMsg, from_queue: bool) {
log_info!("RECEIVED INBOX MSG {:?}", msg);

@ -1479,36 +1479,3 @@ pub struct ShuffledPazzle {
pub emoji_indices: Vec<Vec<u8>>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct NgQRCodeWalletTransferV0 {
pub broker: BrokerServerV0,
pub rendezvous: SymKey, // Rendez-vous ID
pub secret_key: SymKey,
pub is_rendezvous: bool,
}
#[derive(Clone, Debug, Zeroize, ZeroizeOnDrop, Serialize, Deserialize)]
pub struct NgQRCodeWalletRecoveryV0 {
#[zeroize(skip)]
pub wallet: WalletContentV0, //of which security_img is emptied
pub pazzle: Vec<u8>,
pub mnemonic: [u16; 12],
pub pin: [u8; 4],
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum NgQRCode {
WalletTransferV0(NgQRCodeWalletTransferV0),
WalletRecoveryV0(NgQRCodeWalletRecoveryV0),
}
impl NgQRCode {
pub fn from_code(code: String) -> Result<Self, NgError> {
let decoded = base64_url::decode(&code).map_err(|_| NgError::SerializationError)?;
Ok(serde_bare::from_slice(&decoded)?)
}
pub fn to_code(&self) -> String {
let ser = serde_bare::to_vec(self).unwrap();
base64_url::encode(&ser)
}
}

Loading…
Cancel
Save