new third party login mechanism working

feat/orm-diffs
Niko PLP 2 days ago
parent 9c1165653b
commit 663e8d31f1
  1. 2
      Cargo.lock
  2. 2
      README.md
  3. 8
      app/ui-common/src/api.ts
  4. 11
      app/ui-common/src/lib/NoWallet.svelte
  5. 6
      app/ui-common/src/locales/en.json
  6. 1
      app/ui-common/src/routes/NotFound.svelte
  7. 2
      app/ui-common/src/routes/User.svelte
  8. 8
      app/ui-common/src/routes/WalletCreate.svelte
  9. 10
      engine/broker/auth/index.html
  10. 3
      engine/broker/auth/package.json
  11. 111
      engine/broker/auth/src/App.svelte
  12. 89
      engine/broker/auth/src/main.ts
  13. 27
      engine/broker/auth/src/routes/Error.svelte
  14. 2
      engine/broker/auth/src/routes/Home.svelte
  15. 11
      engine/broker/auth/src/store.ts
  16. 26
      engine/broker/auth/src/worker.js
  17. 3
      engine/repo/Cargo.toml
  18. 4
      infra/ngaccount/web/src/routes/Delete.svelte
  19. 2
      infra/ngaccount/web/vite.config.js
  20. 4
      infra/ngapp/src/main.rs
  21. 2
      infra/ngapp/web/src/routes/WalletCreate.svelte
  22. 11
      infra/ngnet/README.md
  23. 65
      infra/ngnet/auth/index.html
  24. 22
      infra/ngnet/auth/package.json
  25. 2
      infra/ngnet/auth/postcss.config.cjs
  26. 32
      infra/ngnet/auth/prepare-app-file.cjs
  27. 180
      infra/ngnet/auth/src/App.svelte
  28. 4
      infra/ngnet/auth/src/assets/EU.svg
  29. 160
      infra/ngnet/auth/src/lib/Home.svelte
  30. 141
      infra/ngnet/auth/src/main.ts
  31. 99
      infra/ngnet/auth/src/routes/Home.svelte
  32. 53
      infra/ngnet/auth/src/store.ts
  33. 1
      infra/ngnet/auth/src/vite-env.d.ts
  34. 26
      infra/ngnet/auth/src/worker.js
  35. 60
      infra/ngnet/auth/vite.config.js
  36. 2
      infra/ngnet/bootstrap/index.html
  37. 16
      infra/ngnet/bootstrap/package.json
  38. 13
      infra/ngnet/bootstrap/postcss.config.cjs
  39. 180
      infra/ngnet/bootstrap/src/App.svelte
  40. 0
      infra/ngnet/bootstrap/src/app.postcss
  41. 0
      infra/ngnet/bootstrap/src/assets/nextgraph.svg
  42. 202
      infra/ngnet/bootstrap/src/main.ts
  43. 28
      infra/ngnet/bootstrap/src/routes/Error.svelte
  44. 20
      infra/ngnet/bootstrap/src/routes/Home.svelte
  45. 15
      infra/ngnet/bootstrap/src/store.ts
  46. 0
      infra/ngnet/bootstrap/svelte.config.js
  47. 1
      infra/ngnet/bootstrap/tailwind.config.cjs
  48. 39
      infra/ngnet/bootstrap/vite.config.js
  49. 33
      infra/ngnet/redir/index.html
  50. 6
      infra/ngnet/redir/package.json
  51. 32
      infra/ngnet/redir/prepare-app-file.cjs
  52. 145
      infra/ngnet/redir/src/App.svelte
  53. 4
      infra/ngnet/redir/src/assets/EU.svg
  54. 16
      infra/ngnet/redir/src/assets/nextgraph.svg
  55. 88
      infra/ngnet/redir/src/lib/Home.svelte
  56. 99
      infra/ngnet/redir/src/main.ts
  57. 104
      infra/ngnet/redir/src/routes/Home.svelte
  58. 34
      infra/ngnet/redir/src/store.ts
  59. 26
      infra/ngnet/redir/src/worker.js
  60. 40
      infra/ngnet/src/main.rs
  61. 1
      infra/ngnet/web/package.json
  62. 4
      infra/ngnet/web/src/assets/EU.svg
  63. 1
      infra/ngnet/web/src/routes/NotFound.svelte
  64. 4
      package.json
  65. 813
      pnpm-lock.yaml
  66. 1
      pnpm-workspace.yaml
  67. 2
      sdk/js/DEV.md
  68. 4
      sdk/js/examples/multi-framework-signals/package.json
  69. 19
      sdk/js/examples/multi-framework-signals/src/app/pages/index.astro
  70. 24
      sdk/js/examples/react-ldo/.gitignore
  71. 57
      sdk/js/examples/react-ldo/README.md
  72. 28
      sdk/js/examples/react-ldo/eslint.config.js
  73. 12
      sdk/js/examples/react-ldo/index.html
  74. 40
      sdk/js/examples/react-ldo/package.json
  75. 6
      sdk/js/examples/react-ldo/postcss.config.js
  76. 68
      sdk/js/examples/react-ldo/src/.ldo/contact.context.ts
  77. 115
      sdk/js/examples/react-ldo/src/.ldo/contact.schema.ts
  78. 19
      sdk/js/examples/react-ldo/src/.ldo/contact.shapeTypes.ts
  79. 37
      sdk/js/examples/react-ldo/src/.ldo/contact.typings.ts
  80. 82
      sdk/js/examples/react-ldo/src/.ldo/container.context.ts
  81. 124
      sdk/js/examples/react-ldo/src/.ldo/container.schema.ts
  82. 19
      sdk/js/examples/react-ldo/src/.ldo/container.shapeTypes.ts
  83. 44
      sdk/js/examples/react-ldo/src/.ldo/container.typings.ts
  84. 26
      sdk/js/examples/react-ldo/src/.shapes/contact.shex
  85. 24
      sdk/js/examples/react-ldo/src/.shapes/container.shex
  86. 56
      sdk/js/examples/react-ldo/src/App.css
  87. 22
      sdk/js/examples/react-ldo/src/App.tsx
  88. 29
      sdk/js/examples/react-ldo/src/Contact.tsx
  89. 38
      sdk/js/examples/react-ldo/src/Contacts.tsx
  90. 44
      sdk/js/examples/react-ldo/src/Header.tsx
  91. 60
      sdk/js/examples/react-ldo/src/MakeContact.tsx
  92. 20
      sdk/js/examples/react-ldo/src/NextGraphAuthContext.ts
  93. 112
      sdk/js/examples/react-ldo/src/createBrowserNGReactMethods.tsx
  94. 3
      sdk/js/examples/react-ldo/src/index.css
  95. 10
      sdk/js/examples/react-ldo/src/main.tsx
  96. 18
      sdk/js/examples/react-ldo/src/reactMethods.ts
  97. 1
      sdk/js/examples/react-ldo/src/vite-env.d.ts
  98. 12
      sdk/js/examples/react-ldo/tailwind.config.js
  99. 26
      sdk/js/examples/react-ldo/tsconfig.app.json
  100. 7
      sdk/js/examples/react-ldo/tsconfig.json
  101. Some files were not shown because too many files have changed in this diff Show More

2
Cargo.lock generated

@ -2194,8 +2194,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"js-sys",
"libc", "libc",
"wasi 0.9.0+wasi-snapshot-preview1", "wasi 0.9.0+wasi-snapshot-preview1",
"wasm-bindgen",
] ]
[[package]] [[package]]

@ -11,7 +11,7 @@
[![Crates.io Version](https://img.shields.io/crates/v/nextgraph)](https://crates.io/crates/nextgraph) [![Crates.io Version](https://img.shields.io/crates/v/nextgraph)](https://crates.io/crates/nextgraph)
[![docs.rs](https://img.shields.io/docsrs/nextgraph)](https://docs.rs/nextgraph) [![docs.rs](https://img.shields.io/docsrs/nextgraph)](https://docs.rs/nextgraph)
[node:![NPM Version node](https://img.shields.io/npm/v/nextgraph)](https://www.npmjs.com/package/nextgraph) [node:![NPM Version node](https://img.shields.io/npm/v/nextgraph)](https://www.npmjs.com/package/nextgraph)
[web:![NPM Version web](https://img.shields.io/npm/v/nextgraphweb)](https://www.npmjs.com/package/nextgraphweb) [web:![NPM Version web](https://img.shields.io/npm/v/@ng-org/web)](https://www.npmjs.com/package/@ng-org/web)
[![PyPI - Version](https://img.shields.io/pypi/v/nextgraphpy)](https://pypi.org/project/nextgraphpy/) [![PyPI - Version](https://img.shields.io/pypi/v/nextgraphpy)](https://pypi.org/project/nextgraphpy/)
Rust implementation of NextGraph Rust implementation of NextGraph

@ -28,10 +28,10 @@ export const NG_EU_BSP_REGISTER = import.meta.env.PROD
? "https://account.nextgraph.eu/#/create" ? "https://account.nextgraph.eu/#/create"
: "http://account-dev.nextgraph.eu:5173/#/create"; : "http://account-dev.nextgraph.eu:5173/#/create";
export const NG_NET_BSP = "https://nextgraph.net"; export const NG_ONE_BSP = "https://nextgraph.one";
export const NG_NET_BSP_REGISTER = import.meta.env.PROD export const NG_ONE_BSP_REGISTER = import.meta.env.PROD
? "https://account.nextgraph.net/#/create" ? "https://account.nextgraph.one/#/create"
: "http://account-dev.nextgraph.net:5173/#/create"; : "http://account-dev.nextgraph.one:5173/#/create";
export const APP_ACCOUNT_REGISTERED_SUFFIX = "/#/user/registered"; export const APP_ACCOUNT_REGISTERED_SUFFIX = "/#/user/registered";
export const APP_WALLET_CREATE_SUFFIX = "/#/wallet/create"; export const APP_WALLET_CREATE_SUFFIX = "/#/wallet/create";

@ -20,6 +20,8 @@
import { link } from "svelte-spa-router"; import { link } from "svelte-spa-router";
import CenteredLayout from "./CenteredLayout.svelte"; import CenteredLayout from "./CenteredLayout.svelte";
import { t } from "svelte-i18n"; import { t } from "svelte-i18n";
export let without_create = false;
</script> </script>
<CenteredLayout displayFooter={true}> <CenteredLayout displayFooter={true}>
@ -30,8 +32,14 @@
<h1 class="text-2xl text-center mb-10">{$t("pages.no_wallet.welcome")}</h1> <h1 class="text-2xl text-center mb-10">{$t("pages.no_wallet.welcome")}</h1>
<p class="max-w-sm"> <p class="max-w-sm">
{@html $t("pages.no_wallet.description")} {@html $t("pages.no_wallet.description")}<br />
{@html $t("pages.no_wallet.instructions_login")}{#if !without_create}{@html $t("pages.no_wallet.instructions_create")}
{:else}
{@html $t("pages.no_wallet.instructions_nocreate")}
{/if}
</p> </p>
{#if !without_create}
<div class="row mt-5"> <div class="row mt-5">
<a href="/wallet/create" use:link> <a href="/wallet/create" use:link>
<button <button
@ -57,6 +65,7 @@
</button> </button>
</a> </a>
</div> </div>
{/if}
<div class="row mt-5"> <div class="row mt-5">
<a href="/wallet/login" use:link> <a href="/wallet/login" use:link>
<button <button

@ -342,7 +342,10 @@
}, },
"no_wallet": { "no_wallet": {
"welcome": "Welcome to NextGraph", "welcome": "Welcome to NextGraph",
"description": "We could not find a wallet saved on this device.<br /> If you already have a wallet, select \"Login\", otherwise, select \"Create Wallet\" here below.", "description": "We could not find a wallet saved on this device.",
"instructions_login": "If you already have a wallet, select \"Login\"",
"instructions_create": ", otherwise, select \"Create Wallet\" here below.",
"instructions_nocreate": ". Creation of a Wallet is not possible while<br/> logging-in to a third-party app.",
"create_wallet": "Create Wallet" "create_wallet": "Create Wallet"
}, },
"login": { "login": {
@ -376,6 +379,7 @@
"enter_pin": "Enter your PIN code", "enter_pin": "Enter your PIN code",
"opening_wallet": "Opening your wallet...<br /> Please wait", "opening_wallet": "Opening your wallet...<br /> Please wait",
"wallet_opened": "Your wallet is opened! <br />Please wait while the app is loading...", "wallet_opened": "Your wallet is opened! <br />Please wait while the app is loading...",
"redirecting": "Redirecting to",
"qr_code": "Wallet QRCode", "qr_code": "Wallet QRCode",
"qr_modal_title": "My Wallet QRCode", "qr_modal_title": "My Wallet QRCode",
"qr_modal_description": "Use this QRCode to log in with your wallet on new devices.", "qr_modal_description": "Use this QRCode to log in with your wallet on new devices.",

@ -16,7 +16,6 @@
import { import {
ArrowLeft, ArrowLeft,
} from "svelte-heros-v2"; } from "svelte-heros-v2";
export let params;
</script> </script>
<CenteredLayout displayFooter={true}> <CenteredLayout displayFooter={true}>

@ -62,7 +62,7 @@
import { import {
NG_EU_BSP, NG_EU_BSP,
NG_NET_BSP, NG_ONE_BSP,
APP_ACCOUNT_REGISTERED_SUFFIX, APP_ACCOUNT_REGISTERED_SUFFIX,
default as ng, default as ng,
} from "../api"; } from "../api";

@ -30,11 +30,11 @@
import { LockOpen, FingerPrint, ExclamationTriangle } from "svelte-heros-v2"; import { LockOpen, FingerPrint, ExclamationTriangle } from "svelte-heros-v2";
import { import {
NG_EU_BSP, NG_EU_BSP,
NG_NET_BSP, NG_ONE_BSP,
LINK_NG_BOX, LINK_NG_BOX,
LINK_SELF_HOST, LINK_SELF_HOST,
NG_EU_BSP_REGISTER, NG_EU_BSP_REGISTER,
NG_NET_BSP_REGISTER, NG_ONE_BSP_REGISTER,
APP_WALLET_CREATE_SUFFIX, APP_WALLET_CREATE_SUFFIX,
default as ng, default as ng,
} from "../api"; } from "../api";
@ -450,8 +450,8 @@
const selectEU = async (event) => { const selectEU = async (event) => {
await select_bsp(NG_EU_BSP_REGISTER, "nextgraph.eu"); await select_bsp(NG_EU_BSP_REGISTER, "nextgraph.eu");
}; };
const selectNET = async (event) => { const selectONE = async (event) => {
await select_bsp(NG_NET_BSP_REGISTER, "nextgraph.net"); await select_bsp(NG_ONE_BSP_REGISTER, "nextgraph.one");
}; };
const enterINVITE = (event) => {}; const enterINVITE = (event) => {};
const enterQRcode = (event) => {}; const enterQRcode = (event) => {};

@ -25,16 +25,16 @@
.noshow { .noshow {
display: none !important; display: none !important;
} }
.nextgraph-app-auth-iframe { .nextgraph-net-auth-iframe {
visibility: hidden; visibility: hidden;
} }
.nextgraph-app-auth-iframe.nextgraph-app-auth-iframe--active { .nextgraph-net-auth-iframe.nextgraph-net-auth-iframe--active {
visibility: visible; visibility: visible;
} }
#banner { #banner {
padding-right: 36px !important; padding-right: 36px !important;
width: 100%; position: fixed; left:0; top:0; min-height:36px; background-color: rgb(73, 114, 165); color: white; text-align:center ;z-index:10; padding:3px; font-size: 1.25rem; width: 100%; position: fixed; left:0; top:0; min-height:36px; background-color: rgb(73, 114, 165); color: white; text-align:center ;z-index:10; padding:3px; font-size: 1.25rem;
line-height: 1.75rem; overflow-wrap: break-word; line-height: 1.75rem; overflow-wrap: break-word;
} }
#close-auth { #close-auth {
position: fixed; right:0; top:0; width: 36px; height: 36px; background-color: rgb(73, 114, 165);z-index:11; position: fixed; right:0; top:0; width: 36px; height: 36px; background-color: rgb(73, 114, 165);z-index:11;
@ -44,7 +44,7 @@
</head> </head>
<body> <body>
<iframe id="nextgraph-app-auth-iframe" class="nextgraph-app-auth-iframe" scrolling="auto" frameborder="0" <iframe id="nextgraph-net-auth-iframe" class="nextgraph-net-auth-iframe" scrolling="auto" frameborder="0"
style="position: fixed; left: 0; top: 0; height: 100%; width: 100%; overflow:auto;"> style="position: fixed; left: 0; top: 0; height: 100%; width: 100%; overflow:auto;">
</iframe> </iframe>
<div id="banner"> <div id="banner">
@ -56,7 +56,7 @@
</div> </div>
<script> <script>
document.getElementById("close-auth").onclick = (e)=> { document.getElementById("close-auth").onclick = (e)=> {
window.ng_status_callback.write({status:"cancelled"}); window.location.href=window.origin_url;
}; };
</script> </script>
<div id="splash" class="splashing"> <div id="splash" class="splashing">

@ -16,8 +16,7 @@
"@tailwindcss/typography": "^0.5.13", "@tailwindcss/typography": "^0.5.13",
"svelte-i18n": "^4.0.0", "svelte-i18n": "^4.0.0",
"@ng-org/ui-common": "workspace:*", "@ng-org/ui-common": "workspace:*",
"@ng-org/api-web": "workspace:*", "@ng-org/api-web": "workspace:*"
"remote-web-streams": "^0.2.0"
}, },
"devDependencies": { "devDependencies": {
"shx": "^0.3.4", "shx": "^0.3.4",

@ -10,10 +10,10 @@
--> -->
<script lang="ts"> <script lang="ts">
import { push, default as Router } from "svelte-spa-router"; import { push, default as Router, querystring } from "svelte-spa-router";
import {wrap} from 'svelte-spa-router/wrap'; import {wrap} from 'svelte-spa-router/wrap';
import { isLoading } from "svelte-i18n"; import { isLoading } from "svelte-i18n";
import { origin } from "./store";
import { onMount, tick, onDestroy } from "svelte"; import { onMount, tick, onDestroy } from "svelte";
import ng from "@ng-org/ui-common/api"; import ng from "@ng-org/ui-common/api";
import { import {
@ -40,6 +40,23 @@
// import { select_default_lang } from "@ng-org/ui-common/lang"; // import { select_default_lang } from "@ng-org/ui-common/lang";
import Home from "./routes/Home.svelte"; import Home from "./routes/Home.svelte";
import Error from "./routes/Error.svelte";
const param = new URLSearchParams($querystring);
let origin_url = decodeURIComponent(param.get("o"));
let host;
try {
//console.log(origin_url);
host = new URL(origin_url).host;
//console.log(host);
document.getElementById("banner").innerText = "Opening Wallet for "+host;
origin.set(origin_url);
window.origin_url = origin_url;
} catch {
push("#/error");
}
let logged_in = false;
const routes = new Map(); const routes = new Map();
routes.set("/", Home); routes.set("/", Home);
@ -57,11 +74,14 @@
routes.set("/wallet", WalletInfo); routes.set("/wallet", WalletInfo);
routes.set("/user/accounts", AccountInfo); routes.set("/user/accounts", AccountInfo);
routes.set("/scanqr", ScanQRWeb); routes.set("/scanqr", ScanQRWeb);
routes.set("/error", Error);
routes.set("*", NotFound); routes.set("*", NotFound);
let unsubscribe = () => {}; let unsubscribe = () => {};
let unsubscribe_session = () => {}; let unsubscribe_session = () => {};
let net_auth_iframe;
let wallet_channel; let wallet_channel;
let unsub_main_close; let unsub_main_close;
@ -69,13 +89,17 @@
// let walls = await ng.get_wallets(); // let walls = await ng.get_wallets();
// wallets.set(walls); // wallets.set(walls);
// }; // };
const iframe_config = import.meta.env.DEV ? {src:"http://localhost:14404/?o=", origin: "http://localhost:14404"} :
import.meta.env.NG_DEV ? {src:"http://localhost:1421/auth.html?o=", origin: "http://localhost:1421"} :
{src:"https://nextgraph.net/auth/?o=", origin: "https://nextgraph.net"} ;
// to test ngnet
//const iframe_config = {src:"http://127.0.0.1:3033/auth/?o=", origin: "http://127.0.0.1:3033"};
onMount(async () => { onMount(async () => {
window.document.getElementById("splash").className="noshow"; window.document.getElementById("splash").className="noshow";
window.document.getElementById("app").className=""; window.document.getElementById("app").className="";
//window.document.getElementById("splash").className="splash-loaded";
try { try {
await disconnections_subscribe(); await disconnections_subscribe();
} catch (e) { } catch (e) {
@ -201,7 +225,6 @@
unsubscribe = active_wallet.subscribe(async (value) => { unsubscribe = active_wallet.subscribe(async (value) => {
if (value) { if (value) {
if (value.wallet) { if (value.wallet) {
//(<any>window).ng_status_callback.write({status:"loggedin"});
opened_wallets.update((w) => { opened_wallets.update((w) => {
w[value.id] = value.wallet; w[value.id] = value.wallet;
return w; return w;
@ -217,7 +240,6 @@
location.href location.href
); );
} else { } else {
//(<any>window).ng_status_callback.write({status:"loggedout"});
wallet_channel.postMessage( wallet_channel.postMessage(
{ cmd: "closed", walletid: value.id }, { cmd: "closed", walletid: value.id },
location.href location.href
@ -238,17 +260,78 @@
unsubscribe_session = active_session.subscribe(async (value) => { unsubscribe_session = active_session.subscribe(async (value) => {
//console.log("active_session has changed", value) //console.log("active_session has changed", value)
if (value) { if (value) {
if ((<any>window).ng_status_callback) { logged_in = true;
//console.log("writing loggedin to callback"); document.getElementById("banner").innerText = "Wallet opened for "+host;
(<any>window).ng_status_callback.write({status:"loggedin", session:value}); let iframe = window.document.getElementById("nextgraph-net-auth-iframe");
} iframe?.classList.add('nextgraph-net-auth-iframe--active');
} else { window.document.getElementById("app").style["display"] = "none";
if ((<any>window).ng_status_callback) { let origin = window.location.origin;
console.log("writing loggedout to callback"); let encoded_origin = encodeURIComponent(origin);
(<any>window).ng_status_callback.write({status:"loggedout"});
} iframe.addEventListener("load", function() {
net_auth_iframe = this.contentWindow;
const ready_handler = async function(m) {
if (m.data.ready && m.origin === iframe_config.origin) {
//console.log("got ready message from", m.origin);
//remove this listener
window.removeEventListener("message",ready_handler);
const { port1, port2 } = new MessageChannel();
port1.onmessage = async (e) => {
if (e.data.done) {
// end of session
window.location.href = origin_url;
} else {
const method = e.data.method;
const args = e.data.args;
const port = e.data.port;
// TODO: add other stream RPC methods
if ( method === "doc_subscribe" ) {
//console.log("processing streamed request ...",method, args);
args.push((callbacked)=> {
port.postMessage({stream:true, ret:callbacked});
});
try {
let cancel_function = () => {};
port.onclose = () => {
cancel_function();
};
cancel_function = await Reflect.apply(ng[method], null, args);
} catch (e) {
port.postMessage({ok:false, ret:e});
port.close();
}
} else {
// forwarding to ng
//console.log("processing...",method, args);
try {
let res = await Reflect.apply(ng[method], null, args);
//console.log("got res=",res)
port.postMessage({ok:true, ret:res});
port.close();
} catch (e) {
port.postMessage({ok:false, ret:e});
port.close();
}
}
}
};
//console.log("sending init message to app-auth");
net_auth_iframe.postMessage({ method: "init", session:value, manifest:{origin:origin_url}, port: port2 }, iframe_config.origin, [port2]);
} else if (m.data.status == "error" && m.origin === iframe_config.origin) {
console.error(m.data.error);
window.location.href = origin_url;
}
};
window.addEventListener("message",ready_handler);
});
iframe.src = `${iframe_config.src}${encoded_origin}`;
} else if (logged_in) {
// we redirect to the unauthenticated origin
window.location.href = origin_url;
} }
}); });
//TODO: remove this
//active_session.set({session_id:1});
}); });
onDestroy(() => { onDestroy(() => {

@ -10,100 +10,13 @@
import "./app.postcss"; import "./app.postcss";
import "../../../../app/ui-common/src/styles.css"; import "../../../../app/ui-common/src/styles.css";
import App from "./App.svelte"; import App from "./App.svelte";
import { fromWritablePort } from 'remote-web-streams';
import web_api from "@ng-org/api-web"; import web_api from "@ng-org/api-web";
import {init_api} from "@ng-org/ui-common/api"; import {init_api} from "@ng-org/ui-common/api";
import {manifest} from "./store";
init_api(web_api); init_api(web_api);
import {
active_wallet,
has_wallets,
derived,
} from "@ng-org/ui-common/store";
import {
get,
} from "svelte/store";
import { push } from "svelte-spa-router";
import { select_default_lang } from "@ng-org/ui-common/lang"; import { select_default_lang } from "@ng-org/ui-common/lang";
select_default_lang(()=>{return window.navigator.languages;}).then(() => {}); select_default_lang(()=>{return window.navigator.languages;}).then(() => {});
const origin = import.meta.env.NG_DEV ? "http://localhost:1421" : "https://nextgraph.net";
// for development purpose, when testing net-auth
//const origin = "http://localhost:14402"
//let status_callback : WritableStreamDefaultWriter<any> | undefined = undefined;
const AUTH_HOME = "#/";
// const AUTH_USER_PANEL = "#/user";
// const AUTH_USER_ACCOUNTS = "#/user/accounts";
// const AUTH_WALLET = "#/wallet";
window.addEventListener("message", async (event)=>{
//console.log("got msg in app-auth",event)
const { method, port } = event.data;
const writable = fromWritablePort(port);
const writer = writable.getWriter();
if (event.origin !== origin) {
writer.write({status:'error', error:'invalid origin'});
writer.close();
} else if ( method === "init" ) {
//console.log("app-auth init done, ng_status_callback set");
(<any>window).ng_status_callback = writer;
manifest.set(event.data.manifest);
} else if ( method === "login" ) {
if (get(active_wallet)) {
writer.write({ok:true, ret:true});
writer.close();
} else {
//if not logged in
// go to login and return false
push(AUTH_HOME);
writer.write({ok:true, ret:false});
writer.close();
}
} else if ( method === "doc_subscribe" ) {
let args = event.data.args;
//console.log("processing doc_subscribe...",method, args);
args.push((callbacked)=> {
writer.write({stream:true, ret:callbacked});
});
// TODO: deal with cancel and end of stream (call writer.close())
try {
let cancel_function = await Reflect.apply(web_api[method], null, args);
} catch (e) {
writer.write({ok:false, ret:e});
writer.close();
}
} else {
// forwarding to ng
//console.log("processing...",method, event.data.args);
try {
let res = await Reflect.apply(web_api[method], null, event.data.args);
writer.write({ok:true, ret:res});
writer.close();
} catch (e) {
writer.write({ok:false, ret:e});
writer.close();
}
}
}, false);
//console.log("addEventListener for message in app-auth done")
parent.postMessage({ready:true},origin);
const app = new App({ const app = new App({
target: document.getElementById("app"), target: document.getElementById("app"),
}); });

@ -0,0 +1,27 @@
<!--
// 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.
-->
<!--
Error page (?o=https://domain.tld was not provided or is invalid)
-->
<script>
import { ExclamationTriangle} from "svelte-heros-v2";
</script>
<div class="text-center max-w-6xl lg:px-8 mx-auto px-4 text-green-800">
<div class=" max-w-6xl lg:px-8 mx-auto px-4 text-red-800">
<ExclamationTriangle class="animate-bounce mt-10 h-16 w-16 mx-auto" />
<p class="max-w-xl md:mx-auto lg:max-w-2xl mb-5">
Invalid request
</p>
</div>
</div>

@ -48,7 +48,7 @@
</script> </script>
{#if display_login_create} {#if display_login_create}
<NoWallet /> <NoWallet without_create={true}/>
{:else} {:else}
<Home /> <Home />
{/if} {/if}

@ -9,17 +9,10 @@
import { import {
writable, writable,
readable,
readonly,
derived,
get,
type Writable, type Writable,
} from "svelte/store"; } from "svelte/store";
import { createAsyncProxy } from "async-proxy";
import { RemoteReadableStream } from 'remote-web-streams';
export const manifest = writable<undefined | Object>( undefined ); export const manifest = writable<undefined | Object>( undefined );
// import worker_ from "./worker.js?worker&inline"; export const origin = writable<undefined | string>( undefined );
// const worker = new worker_();

@ -1,26 +0,0 @@
// 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.
//import * as sdk from "@ng-org/wasm-tools-auth";
import { fromWritablePort } from 'remote-web-streams';
self.onmessage = (event) => {
(async function() {
const { method, args, port } = event.data;
const writable = fromWritablePort(port);
const writer = writable.getWriter();
//console.log("Message received by worker", method, args);
let ret = await Reflect.apply(sdk[method], null, args);
writer.write(ret);
writer.close();
})();
}
//console.log("worker loaded");

@ -30,7 +30,6 @@ once_cell = "1.17.1"
futures = "0.3.24" futures = "0.3.24"
num_enum = "0.5.7" num_enum = "0.5.7"
slice_as_array = "1.1.0" slice_as_array = "1.1.0"
rand = { version = "0.7", features = ["getrandom"] }
blake3 = "1.3.1" blake3 = "1.3.1"
chacha20 = "0.9.0" chacha20 = "0.9.0"
ed25519-dalek = "1.0.1" ed25519-dalek = "1.0.1"
@ -55,7 +54,9 @@ features = ["wasm_js"]
debug_print = "1.0.0" debug_print = "1.0.0"
log = "0.4" log = "0.4"
getrandom = "0.3.3" getrandom = "0.3.3"
rand = { version = "0.7", features = ["getrandom"] }
[target.'cfg(target_arch = "wasm32")'.dependencies] [target.'cfg(target_arch = "wasm32")'.dependencies]
rand = { version = "0.7", features = ["wasm-bindgen"] }
gloo-timers = "0.3.0" gloo-timers = "0.3.0"
time = { version= "=0.3.41", features = ["formatting","local-offset","wasm-bindgen"] } time = { version= "=0.3.41", features = ["formatting","local-offset","wasm-bindgen"] }

@ -115,8 +115,8 @@
to use NextGraph. You have other options to select a new broker, to use NextGraph. You have other options to select a new broker,
like hosting it yourself, or buying an NG Box. Please visit <a like hosting it yourself, or buying an NG Box. Please visit <a
target="_blank" target="_blank"
href="https://nextgraph.net/#/account/register" href="https://nextgraph.eu/#/account/register"
>https://nextgraph.net/#/account/register</a >https://nextgraph.eu/#/account/register</a
> in order to choose a new broker. > in order to choose a new broker.
</span> </span>
</li> </li>

@ -7,7 +7,7 @@ import svelteSVG from "vite-plugin-svelte-svg";
export default defineConfig({ export default defineConfig({
envPrefix: ["VITE_", "NG_"], envPrefix: ["VITE_", "NG_"],
server: { server: {
allowedHosts: ["account-dev.nextgraph.eu"] allowedHosts: ["account-dev.nextgraph.eu","account-dev.nextgraph.one"]
}, },
plugins: [svelte({ plugins: [svelte({
preprocess: [ preprocess: [

@ -35,7 +35,7 @@ use crate::store::wallet_record::*;
use crate::types::*; use crate::types::*;
#[derive(RustEmbed)] #[derive(RustEmbed)]
#[folder = "../../app/nextgraph/dist-file"] #[folder = "../../app/nextgraph/dist-web"]
struct Static; struct Static;
#[tokio::main] #[tokio::main]
@ -57,7 +57,7 @@ async fn main() {
.allow_origin(NG_NET_URL) .allow_origin(NG_NET_URL)
.allow_origin(NG_APP_URL) .allow_origin(NG_APP_URL)
.allow_origin("https://nextgraph.eu") .allow_origin("https://nextgraph.eu")
.allow_origin("https://nextgraph.net"); .allow_origin("https://nextgraph.one");
} }
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
{ {

@ -79,7 +79,7 @@
</div> </div>
<div class="row mt-5"> <div class="row mt-5">
<a href="https://nextgraph.net/#/wallet/create"> <a href="https://nextgraph.one/#/wallet/create">
<button <button
tabindex="-1" tabindex="-1"
class="text-primary-700 bg-primary-100 hover:bg-primary-100/90 focus:ring-4 focus:ring-primary-100/50 font-medium rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-100/55 mr-2 mb-2" class="text-primary-700 bg-primary-100 hover:bg-primary-100/90 focus:ring-4 focus:ring-primary-100/50 font-medium rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-100/55 mr-2 mb-2"

@ -13,11 +13,12 @@ pnpm install
## Dev ## Dev
compile the 2 helpers, in dev mode compile the 3 front-ends, in dev mode
``` ```
pnpm -C ../net-auth builddev pnpm -C ./auth builddev
pnpm -C ../net-bootstrap builddev pnpm -C ./bootstrap builddev
pnpm -C ./redir builddev
``` ```
```bash ```bash
@ -34,9 +35,7 @@ cargo watch -c -w src -x run
## Prod ## Prod
``` ```
pnpm -C ../net-auth build pnpm buildfront
pnpm -C ../net-bootstrap build
pnpm -C ./web build
cargo build -r cargo build -r
``` ```

@ -13,76 +13,15 @@
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>NextGraph Auth</title> <title>NextGraph.net Auth</title>
<style> <style>
.splashing {
height: 95vh;
width:100%;
display: flex;
justify-content: center;
align-items: center;
}
.noshow {
display: none !important;
}
.nextgraph-app-auth-iframe {
visibility: hidden;
}
.nextgraph-app-auth-iframe.nextgraph-app-auth-iframe--active {
visibility: visible;
}
#banner {
padding-right: 36px !important;
width: 100%; position: fixed; left:0; top:0; min-height:36px; background-color: rgb(73, 114, 165); color: white; text-align:center ;z-index:10; padding:3px; font-size: 1.25rem;
line-height: 1.75rem; overflow-wrap: break-word;
}
#close-auth {
position: fixed; right:0; top:0; width: 36px; height: 36px; background-color: rgb(73, 114, 165);z-index:11;
cursor:pointer;
}
</style> </style>
</head> </head>
<body> <body>
<iframe id="nextgraph-app-auth-iframe" class="nextgraph-app-auth-iframe" scrolling="auto" frameborder="0" <iframe id="nextgraph-app-iframe" class="nextgraph-app-iframe" scrolling="auto" frameborder="0"
style="position: fixed; left: 0; top: 0; height: 100%; width: 100%; overflow:auto;"> style="position: fixed; left: 0; top: 0; height: 100%; width: 100%; overflow:auto;">
</iframe> </iframe>
<div id="banner">
</div>
<div id="close-auth">
<svg data-slot="icon" fill="none" stroke-width="1.5" stroke="white" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12"></path>
</svg>
</div>
<script>
document.getElementById("close-auth").onclick = (e)=> {
window.ng_status_callback.write({status:"cancelled"});
};
</script>
<div id="splash" class="splashing">
<div style="flex-direction: column;justify-content: center;color:#4972a5;width:100%;text-align:center;font-family: Inter, Avenir, Helvetica, Arial, sans-serif;">
<svg
style="width:100px;height:100px;margin: 0 auto 20px ;display:flex;"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 225 225"
>
<g>
<circle
r="106.98013"
cy="112.90476"
cx="109.88096"
style="fill:#ffffff;stroke:none;stroke-width:0.268375" />
<path
d="M 98.343352,190.26108 C 80.403778,187.53354 65.011938,179.57839 52.608228,166.62327 38.602093,151.99448 31.178059,133.41381 31.178059,112.98841 c 0,-10.21889 1.700058,-19.44396 5.221234,-28.332119 4.28678,-10.820699 10.037295,-19.39063 18.535095,-27.62263 4.72982,-4.58187 6.60687,-6.10643 11.28099,-9.16256 11.89869,-7.779841 24.173884,-11.879991 38.095802,-12.724761 19.80437,-1.2017 39.11165,5.11306 54.60284,17.858751 1.50718,1.24006 2.72951,2.35934 2.71628,2.48729 -0.0132,0.12795 -3.85821,3.63443 -8.54442,7.79217 -4.6862,4.157729 -10.04724,8.96276 -11.91342,10.677819 -1.86617,1.715071 -3.54094,3.11831 -3.7217,3.11831 -0.18075,0 -1.39985,-0.745188 -2.70911,-1.655969 -7.53011,-5.23834 -15.99428,-7.82188 -25.62597,-7.82188 -12.731628,0 -23.249192,4.3379 -32.143882,13.257541 -6.39594,6.413868 -10.70387,14.555268 -12.50018,23.623578 -0.69099,3.48832 -0.68968,13.53072 0.002,17.00893 3.70508,18.62577 18.31886,33.10194 36.642322,36.29729 4.16439,0.72621 11.98099,0.71223 15.98975,-0.0286 14.03187,-2.59311 25.86047,-11.36806 32.26533,-23.93578 0.77379,-1.51834 1.26018,-2.88461 1.08086,-3.03616 -0.17934,-0.15156 -6.87448,-1.1779 -14.87813,-2.28078 -9.7795,-1.34758 -14.92353,-2.21379 -15.68471,-2.64117 -1.52067,-0.85379 -2.83611,-2.88806 -2.83611,-4.3859 0,-1.1732 2.02687,-15.86876 2.49085,-18.05962 0.29676,-1.40127 2.42559,-3.4934 3.84317,-3.77691 0.62227,-0.12445 8.82712,0.85555 18.28065,2.18348 9.43343,1.32511 17.26269,2.29453 17.39833,2.15427 0.13566,-0.14026 1.11808,-6.54833 2.18313,-14.24014 1.10778,-8.000208 2.20407,-14.60184 2.56177,-15.426229 0.34392,-0.792599 1.11019,-1.849131 1.70287,-2.34782 2.06321,-1.736079 3.1433,-1.785011 12.20439,-0.55291 9.63637,1.310309 10.70873,1.56224 12.28077,2.88503 1.64359,1.382979 2.2732,2.810909 2.25906,5.123309 -0.007,1.10173 -0.92172,8.29645 -2.03332,15.98826 -1.11158,7.69182 -1.97159,14.04091 -1.91113,14.1091 0.0605,0.0682 7.16644,1.11143 15.79109,2.31832 11.10566,1.55407 16.00827,2.38757 16.80223,2.85657 1.53015,0.90389 2.48023,2.64785 2.45017,4.49756 -0.0462,2.84349 -2.41252,18.12279 -2.97521,19.21089 -0.66164,1.27949 -2.60244,2.54696 -3.92109,2.56074 -0.51973,0.005 -7.87449,-0.95937 -16.34391,-2.144 -8.46944,-1.18464 -15.47588,-2.077 -15.56986,-1.98301 -0.094,0.094 -1.18792,7.34163 -2.43097,16.10589 -1.44004,10.15311 -2.49792,16.43621 -2.91556,17.31631 -0.72531,1.52848 -2.76261,3.06291 -4.53817,3.41802 -0.95688,0.19138 -10.90014,-0.92798 -13.59859,-1.53084 -0.5471,-0.12223 -1.89146,0.67252 -4.50941,2.66588 -11.2627,8.57562 -24.34195,13.90917 -38.35741,15.64164 -4.40038,0.54395 -15.72658,0.43298 -19.853658,-0.19451 z"
style="fill:#4972a5;fill-opacity:1;stroke:#4972a5;stroke-width:0.377976;stroke-opacity:1" />
</g>
</svg>
<div>&nbsp;&nbsp;&nbsp;Loading ...</div>
</div>
</div>
<div id="app" class="noshow">
</div>
<!-- # INSERT SCRIPT HERE -->
<script type="module" src="/src/main.ts"></script> <script type="module" src="/src/main.ts"></script>
</body> </body>
</html> </html>

@ -6,35 +6,21 @@
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "vite build --base=./ && shx rm -rf ./dist/assets", "build": "vite build --base=./ && shx rm -rf ./dist/assets",
"builddev": "cross-env NG_DEV=1 vite build --base=./ && shx rm -rf ./dist/assets && shx mkdir -p ../../../app/nextgraph/public_dev && cp ./dist/index.html ../../../app/nextrgaph/public_dev/auth.html", "builddev": "cross-env NG_DEV=1 vite build --base=./ && shx rm -rf ./dist/assets && shx mkdir -p ../../../app/nextgraph/public_dev && cp ./dist/index.html ../../../app/nextgraph/public_dev/auth.html",
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"flowbite": "^1.6.5",
"flowbite-svelte": "^0.37.1",
"svelte-spa-router": "^3.3.0",
"@tailwindcss/typography": "^0.5.13",
"svelte-i18n": "^4.0.0",
"@ng-org/ui-common": "workspace:*",
"async-proxy": "^0.4.1",
"remote-web-streams": "^0.2.0"
}, },
"devDependencies": { "devDependencies": {
"shx": "^0.3.4", "shx": "^0.3.4",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"node-gzip": "^1.1.2", "node-gzip": "^1.1.2",
"@sveltejs/vite-plugin-svelte": "^2.0.4",
"svelte": "^3.58.0",
"vite": "^4.3.9", "vite": "^4.3.9",
"postcss": "^8.4.23", "postcss": "^8.4.23",
"postcss-load-config": "^4.0.1", "postcss-load-config": "^4.0.1",
"svelte-heros-v2": "^0.10.12",
"svelte-preprocess": "^5.0.3",
"tailwindcss": "^3.3.1",
"autoprefixer": "^10.4.14", "autoprefixer": "^10.4.14",
"vite-plugin-svelte-svg": "^2.2.1", "vite-plugin-singlefile": "0.13.5"
"vite-plugin-top-level-await": "1.3.1",
"vite-plugin-singlefile": "0.13.5",
"vite-plugin-wasm": "3.2.2"
} }
} }

@ -1,10 +1,8 @@
const tailwindcss = require("tailwindcss");
const autoprefixer = require("autoprefixer"); const autoprefixer = require("autoprefixer");
const config = { const config = {
plugins: [ plugins: [
//Some plugins, like tailwindcss/nesting, need to run before Tailwind, //Some plugins, like tailwindcss/nesting, need to run before Tailwind,
tailwindcss(),
//But others, like autoprefixer, need to run after, //But others, like autoprefixer, need to run after,
autoprefixer, autoprefixer,
], ],

@ -1,32 +0,0 @@
var crypto = require('crypto')
, fs = require('fs')
const {gzip, } = require('node-gzip');
var algorithm = 'sha256'
, shasum = crypto.createHash(algorithm)
const sha_file = './dist/index.sha256';
const gzip_file = './dist/index.gzip';
var filename = './dist/index.html'
, s = fs.ReadStream(filename)
var bufs = [];
s.on('data', function(data) {
shasum.update(data)
bufs.push(data);
})
s.on('end', function() {
var hash = shasum.digest('hex')
console.log(hash + ' ' + filename)
fs.writeFileSync(sha_file, hash, 'utf8');
var buf = Buffer.concat(bufs);
gzip(buf).then((compressed) => {fs.writeFileSync(gzip_file, compressed);});
fs.rm(filename,()=>{});
})

@ -1,180 +0,0 @@
<!--
// 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 { push, default as Router } from "svelte-spa-router";
import { isLoading } from "svelte-i18n";
import { onMount, tick, onDestroy } from "svelte";
import { ng, brokers_info } from "./store";
import {
NotFound,
Test,
WalletCreate,
Invitation,
WalletLogin,
WalletInfo,
User,
UserRegistered,
Install,
ScanQRWeb,
AccountInfo,
WalletLoginUsername,
WalletLoginQr,
WalletLoginTextCode
} from "@ng-org/ui-common/routes";
import { Bowser } from "../../../../sdk/js/lib-wasm/jsland/bowser.js";
import Home from "./routes/Home.svelte";
const routes = new Map();
routes.set("/", Home);
// routes.set("/test", Test);
// routes.set("/wallet/login", WalletLogin);
// routes.set("/wallet/username", WalletLoginUsername);
// routes.set("/wallet/login-qr", WalletLoginQr);
// routes.set("/wallet/login-text-code", WalletLoginTextCode);
// routes.set("/wallet/create", WalletCreate);
// routes.set("/i/:invitation", Invitation);
// routes.set("/user", User);
// routes.set("/user/registered", UserRegistered);
// routes.set("/wallet", WalletInfo);
// routes.set("/user/accounts", AccountInfo);
// routes.set("/scanqr", ScanQRWeb);
// routes.set("/install", Install);
routes.set("*", NotFound);
// window.refresh_wallets = async () => {
// let walls = await ng.get_wallets();
// wallets.set(walls);
// };
let no_local_storage = false;
let is_safari = false;
function load_bootstraps(bs: string | null) {
if (bs) {
let bootstrap_map = JSON.parse(bs);
brokers_info.set(bootstrap_map);
}
}
onMount(async () => {
window.document.getElementById("splash").className="noshow";
window.document.getElementById("app").className="";
let info = Bowser.parse(window.navigator.userAgent);
//console.log(info);
is_safari = info.browser.name == "Safari";
if (is_safari) return;
window.addEventListener("storage", (event) => {
//console.log("localStorage event", event);
if (event.storageArea != localStorage) return;
if (event.key === "ng_bootstrap") {
load_bootstraps(event.newValue);
}
});
let ls;
try {
ls = localStorage;
try {
let ret = await document.requestStorageAccess({ localStorage: true });
ls = ret.localStorage;
console.log("REQUEST STORAGE ACCESS GRANTED by chrome");
}
catch(e) {
console.warn("requestStorageAccess of chrome failed. falling back to previous api", e)
try {
await document.requestStorageAccess();
localStorage;
console.log("REQUEST STORAGE ACCESS GRANTED");
} catch (e) {
console.error("REQUEST STORAGE ACCESS DENIED",e);
no_local_storage = true;
}
}
} catch (e) {
no_local_storage = true;
console.log("no access to localStorage",e)
}
if (!no_local_storage) {
try {
load_bootstraps(ls.getItem("ng_bootstrap"));
} catch (e) {
console.log("load_bootstraps failed")
}
}
});
</script>
{#if is_safari}
<div class="text-center max-w-6xl lg:px-8 mx-auto px-4 text-red-800">
<svg
class="animate-bounce mt-10 h-16 w-16 mx-auto"
fill="none"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z"
/>
</svg>
<p class="mb-5">
We are sorry but Safari is not supported yet<br/>for WebApps authentication with your Wallet.<br/>Please use another browser.
</p>
</div>
{:else if no_local_storage}
<div class="text-center max-w-6xl lg:px-8 mx-auto px-4 text-red-800">
<svg
class="animate-bounce mt-10 h-16 w-16 mx-auto"
fill="none"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z"
/>
</svg>
<p class="mb-5">
Please give access to localStorage for the website<br/>
{location.origin}
</p>
</div>
{:else}
{#if $isLoading}
<p class="text-center">Loading translations...</p>
{:else}
<Router {routes} />
{/if}
{/if}

@ -1,4 +0,0 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg viewBox="0 0 810 540" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" ><desc>European flag</desc>
<defs><g id="s"><g id="c"><path id="t" d="M0,0v1h0.5z" transform="translate(0,-1)rotate(18)"/><use xlink:href="#t" transform="scale(-1,1)"/></g><g id="a"><use xlink:href="#c" transform="rotate(72)"/><use xlink:href="#c" transform="rotate(144)"/></g><use xlink:href="#a" transform="scale(-1,1)"/></g></defs>
<rect fill="#039" width="810" height="540"/><g fill="#fc0" transform="scale(30)translate(13.5,9)"><use xlink:href="#s" y="-6"/><use xlink:href="#s" y="6"/><g id="l"><use xlink:href="#s" x="-6"/><use xlink:href="#s" transform="rotate(150)translate(0,6)rotate(66)"/><use xlink:href="#s" transform="rotate(120)translate(0,6)rotate(24)"/><use xlink:href="#s" transform="rotate(60)translate(0,6)rotate(12)"/><use xlink:href="#s" transform="rotate(30)translate(0,6)rotate(42)"/></g><use xlink:href="#l" transform="scale(-1,1)"/></g>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

@ -1,160 +0,0 @@
<!--
// 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 { onMount } from "svelte";
import { t, locale } from "svelte-i18n";
import { CenteredLayout } from "@ng-org/ui-common/lib";
import { LogoSimple } from "@ng-org/ui-common/components";
import {
Sidebar,
SidebarGroup,
SidebarItem,
SidebarWrapper,
} from "flowbite-svelte";
import {
ComputerDesktop,
GlobeAlt,
ServerStack
} from "svelte-heros-v2";
import { web_origin, brokers_info, selected_broker } from '../store';
import { fromWritablePort, RemoteReadableStream } from 'remote-web-streams';
let top;
let nonActiveClass =
"flex items-center p-2 text-base font-normal text-gray-900 rounded-lg dark:text-white hover:bg-gray-200 dark:hover:bg-gray-700";
const AUTH_HOME = "#/";
function select(broker: Object) {
let url;
if (import.meta.env.NG_DEV && broker.localhost === 14400) {
// dev mode
url = "http://localhost:1421/appauth.html";
} else if (broker.localhost) {
url = `http://localhost:${broker.localhost}/auth/`;
} else if (broker.private) {
//TODO
url = `http://unimplemented/auth/`;
} else if (broker.domain) {
url = `https://${broker.domain}/auth/`;
} else if (broker.ngbox) {
url = `https://nextgraph.app/auth/`;
} else return;
selected_broker.set(broker);
let iframe = window.document.getElementById("nextgraph-app-auth-iframe");
iframe?.classList.add('nextgraph-app-auth-iframe--active');
window.document.getElementById("app").style["display"] = "none";
(<any>window).ng_iframe_origin = new URL(url).origin;
iframe.addEventListener("load", function() {
(<any>window).ng_broker_selected = this.contentWindow;
const ready_handler = async function(m) {
if (m.data.ready && m.origin === (<any>window).ng_iframe_origin) {
//console.log("got ready message",m);
//remove this listener
window.removeEventListener("message",ready_handler);
const { readable, writablePort } = new RemoteReadableStream();
//console.log("sending init message to app-auth");
(<any>window).ng_broker_selected.postMessage({ method: "init", manifest:window.ng_manifest, port: writablePort }, (<any>window).ng_iframe_origin, [writablePort]);
const reader = readable.getReader();
for (var msg; msg = await reader.read(); ) {
if (msg.done) {
(<any>window).ng_status_callback.close();
break;
} else {
//console.log("forwarding upstream",msg.value);
(<any>window).ng_status_callback.write(msg.value);
}
}
}
};
window.addEventListener("message",ready_handler);
});
iframe.src = url+"?o="+location.search.substring(3)+AUTH_HOME;
}
onMount(() => {
if (Object.keys($brokers_info).length == 1) {
select(Object.values($brokers_info)[0]);
}
});
</script>
{#if Object.keys($brokers_info).length > 1}
<CenteredLayout>
<div class="container3" bind:this={top}>
<div class="row">
<LogoSimple/>
</div>
<div class="row mb-20">
<Sidebar {nonActiveClass}>
<SidebarWrapper
divClass="bg-gray-60 overflow-y-auto py-4 px-3 rounded dark:bg-gray-800"
>
<SidebarGroup ulClass="space-y-2" role="menu">
<li>
<h2 class="text-xl mb-6">{@html $t("auth.select_broker", {values: { origin:$web_origin }})}</h2>
</li>
{#each Object.entries($brokers_info) as broker}
<li
tabindex="0"
role="menuitem"
class="flex items-center p-2 text-base font-normal text-gray-900 clickable rounded-lg dark:text-white hover:bg-gray-200 dark:hover:bg-gray-700"
on:keypress={()=>select(broker[1])}
on:click={()=>select(broker[1])}
>
{#if broker[1].localhost}
<ComputerDesktop tabindex="-1"
class="w-10 h-10 mr-4 text-black transition duration-75 focus:outline-none dark:text-white group-hover:text-gray-900 dark:group-hover:text-white"/>
{:else if broker[1].domain}
<GlobeAlt tabindex="-1"
class="w-10 min-w-10 h-10 mr-4 text-black transition duration-75 focus:outline-none dark:text-white group-hover:text-gray-900 dark:group-hover:text-white"/>
{:else if broker[1].private}
<ServerStack tabindex="-1"
class="w-10 h-10 mr-4 text-black transition duration-75 focus:outline-none dark:text-white group-hover:text-gray-900 dark:group-hover:text-white"/>
{:else if broker[1].ngbox}
<svg
xmlns="http://www.w3.org/2000/svg"
version="1.1"
viewBox="0 0 225 225"
class="mr-4 block h-10 w-10"
stroke="currentColor"
stroke-width="12"
fill="none"
>
<path
d="M 88.332599,179.77884 C 72.858008,177.42608 59.581081,170.564 48.8817,159.38898 36.800075,146.77026 30.396139,130.74266 30.396139,113.12381 c 0,-8.81477 1.466462,-16.772273 4.503812,-24.439156 3.697755,-9.333883 8.658122,-16.726264 15.988284,-23.827148 4.07992,-3.952299 5.699054,-5.267377 9.730928,-7.903581 10.263753,-6.710853 20.852276,-10.247623 32.861256,-10.976317 17.083161,-1.036581 33.737521,4.410501 47.100151,15.404873 1.30009,1.069669 2.35446,2.035155 2.34305,2.145524 -0.0114,0.110369 -3.32807,3.135042 -7.37038,6.721489 -4.04229,3.586437 -8.6667,7.731233 -10.27646,9.210635 -1.60975,1.479412 -3.05439,2.689839 -3.21032,2.689839 -0.15591,0 -1.2075,-0.642795 -2.33686,-1.428431 -6.49544,-4.518567 -13.79659,-6.747116 -22.104843,-6.747116 -10.982241,0 -20.054641,3.741852 -27.727158,11.435891 -5.517107,5.532575 -9.233107,12.555305 -10.782595,20.377588 -0.596045,3.00901 -0.594915,11.67153 0.0017,14.67182 3.195984,16.0665 15.801761,28.55358 31.607491,31.30987 3.592183,0.62643 10.334745,0.61437 13.792675,-0.0247 12.10383,-2.2368 22.30712,-9.80603 27.83192,-20.64689 0.66747,-1.30971 1.08703,-2.48825 0.93235,-2.61898 -0.1547,-0.13073 -5.9299,-1.01605 -12.83381,-1.96739 -8.43575,-1.16241 -12.87296,-1.9096 -13.52955,-2.27826 -1.31171,-0.73647 -2.44642,-2.49122 -2.44642,-3.78325 0,-1.012 1.74837,-13.68832 2.1486,-15.57814 0.25598,-1.20873 2.0923,-3.01339 3.3151,-3.25795 0.53677,-0.10735 7.61424,0.73799 15.7688,1.88346 8.13723,1.14303 14.89071,1.97925 15.00772,1.85826 0.11702,-0.12098 0.96445,-5.648553 1.88315,-12.283473 0.95557,-6.900944 1.90122,-12.59548 2.20977,-13.306594 0.29667,-0.683692 0.95765,-1.595052 1.46889,-2.025218 1.77972,-1.497534 2.7114,-1.539742 10.52745,-0.476938 8.31229,1.130266 9.2373,1.347581 10.59333,2.488613 1.41776,1.192951 1.96085,2.424677 1.94866,4.419342 -0.006,0.950347 -0.79507,7.156475 -1.75393,13.791395 -0.95885,6.634933 -1.70069,12.111623 -1.64854,12.170443 0.0522,0.0588 6.18174,0.95872 13.62132,1.99978 9.57969,1.34053 13.80866,2.0595 14.49353,2.46406 1.3199,0.77969 2.13943,2.28402 2.1135,3.87957 -0.0399,2.45278 -2.08103,15.63263 -2.5664,16.57122 -0.57073,1.10369 -2.24485,2.197 -3.38232,2.20889 -0.44831,0.004 -6.79249,-0.82755 -14.09817,-1.84941 -7.3057,-1.02186 -13.34942,-1.79161 -13.43049,-1.71053 -0.0811,0.0811 -1.02469,6.33285 -2.09694,13.89286 -1.24218,8.75802 -2.1547,14.1778 -2.51495,14.93697 -0.62565,1.31846 -2.38302,2.64205 -3.91461,2.94836 -0.8254,0.16509 -9.4024,-0.80047 -11.73007,-1.32049 -0.47193,-0.10544 -1.63157,0.58011 -3.8898,2.29957 -9.71515,7.39729 -20.99725,11.99799 -33.08692,13.49241 -3.79574,0.46921 -13.565667,0.37348 -17.125664,-0.16779 z"
/>
<rect
ry="37.596001"
y="10.583322"
x="14.363095"
height="204.86308"
width="195.79167"
/>
</svg>
{/if}
<span class="text-left text-xl ml-3" style="overflow-wrap: anywhere;">{broker[0]}</span>
</li>
{/each}
</SidebarGroup>
</SidebarWrapper>
</Sidebar>
</div>
</div>
</CenteredLayout>
{/if}

@ -7,114 +7,63 @@
// notice may not be copied, modified, or distributed except // notice may not be copied, modified, or distributed except
// according to those terms. // according to those terms.
import "./app.postcss"; const searchParams = new URLSearchParams(window.location.search);
import "../../../../app/ui-common/src/styles.css"; let o = searchParams.get("o");
import { link, push } from "svelte-spa-router";
import App from "./App.svelte";
import { fromWritablePort, RemoteReadableStream } from 'remote-web-streams';
import { web_origin } from './store';
import { select_default_lang } from "@ng-org/ui-common/lang"; let web_origin;
select_default_lang(()=>{return window.navigator.languages;}).then(() => {}); let web_redirect;
let wallet_port;
let web_origin_host;
let session;
//let status_callback : WritableStreamDefaultWriter<any> | undefined = undefined; async function rpc( method:string, port: MessagePort, args?: any) : Promise<any> {
const origin = decodeURIComponent(location.search.substring(3)); wallet_port.postMessage({ method, args, port: port }, [port]);
document.getElementById("banner").innerText = "Opening Wallet for "+ new URL(origin).host;
async function rpc( method:string, args?: any) : Promise<any> {
const { readable, writablePort } = new RemoteReadableStream();
(<any>window).ng_broker_selected.postMessage({ method, args, port: writablePort }, (<any>window).ng_iframe_origin, [writablePort]);
const reader = readable.getReader();
let ret = await reader.read();
await reader.read(); // the close
return ret.value;
} }
async function rpc_stream( method:string, args: any, writer:WritableStreamDefaultWriter<any>) {
const { readable, writablePort } = new RemoteReadableStream();
(<any>window).ng_broker_selected.postMessage({ method, args, port: writablePort }, (<any>window).ng_iframe_origin, [writablePort]);
const reader = readable.getReader();
for (var msg; msg = await reader.read(); ) {
if (msg.done) {
writer.close();
break;
}
if (msg.value.error) {
writer.write(msg.value);
writer.close();
break;
} else if (msg.value.stream) {
writer.write(msg.value);
}
// TODO: deal with end of stream
}
}
const AUTH_HOME = "#/";
// const AUTH_USER_PANEL = "#/user";
// const AUTH_USER_ACCOUNTS = "#/user/accounts";
// const AUTH_WALLET = "#/wallet";
window.addEventListener("message", async (event)=>{ window.addEventListener("message", async (event)=>{
if (event.data.ready) return; //console.log("ngnet auth got msg from", event.origin, event.data);
const { method, port } = event.data; const { method, port } = event.data;
const writable = fromWritablePort(port); if (event.origin === o) {
const writer = writable.getWriter(); if (event.data.ready) return;
if (event.origin !== origin) { if ( method === "init" ) {
console.error("invalid origin",event.origin,origin) web_redirect = event.data.manifest.origin;
writer.write({status:'error', error:'invalid origin'}); let url = new URL(web_redirect);
writer.close(); web_origin = url.origin;
} else if ( method === "init" ) { web_origin_host = url.host;
session = event.data.session;
(<any>window).ng_status_callback = writer; port.onclose = () => {
web_origin.set(new URL(origin).host); console.error("BSP parent window closed its port with nextgraph.net");
};
// make API call with origin, event.data.singleton and event.data.access_requests wallet_port = port;
// in order to get full manifest (including security info)
let iframe = window.document.getElementById("nextgraph-app-iframe");
(<any>window).ng_manifest = { iframe.src = web_redirect;
origin: origin, }
singleton: event.data.singleton, } else if (event.origin === web_origin) {
access_request: event.data.access_requests,
name: "", if ( method === "init" ) {
title: "", //console.log("sending back session", session);
description: "", // etc... port.postMessage({ok:true, ret:session});
security_info: {} port.close();
}; } else if ( method === "close" ) {
wallet_port.postMessage({done:true});
} else if ( method === "login" ) { wallet_port.close();
port.close();
if (!(<any>window).ng_broker_selected) {
push(AUTH_HOME);
writer.write({ok:true, ret: false});
writer.close();
} else { } else {
writer.write(await rpc("login")); //console.log("ngnet forward to Broker", method, event.data.args)
writer.close(); // forward to app auth window
await rpc(method, port, event.data.args);
} }
} else if ( method === "doc_subscribe" ) {
//console.log("net forward doc_subscribe to app", method, event.data.args)
await rpc_stream(method, event.data.args, writer);
} else {
//console.log("net forward to app", method, event.data.args)
// forward to app auth iframe
writer.write(await rpc(method, event.data.args));
writer.close();
} }
else {
console.error("invalid origin",event.origin,o, web_origin)
port.postMessage({status:'error', error:'invalid origin'});
port.close();
}
}, false); }, false);
/// for test purposes only, when testing with http://localhost:14402/?o=http://localhost:14402 parent.postMessage({ready:true},o);
// const { readable, writablePort } = new RemoteReadableStream();
// window.postMessage({method:"init", port: writablePort }, location.origin, [writablePort]);
const app = new App({
target: document.getElementById("app"),
});
export default app;

@ -1,99 +0,0 @@
<!--
// 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.
-->
<!--
Home page to display for logged in users.
Redirects to no-wallet or login page, if not logged in.
-->
<script>
import Home from "../lib/Home.svelte";
import { t, locale } from "svelte-i18n";
import { NoWallet } from "@ng-org/ui-common/lib";
import { push } from "svelte-spa-router";
import { onMount, onDestroy } from "svelte";
import { brokers_info, web_origin } from "../store";
</script>
{#if Object.keys($brokers_info).length == 0}
<!-- <NoWallet /> -->
{#if import.meta.env.NG_DEV}
<div class="text-center max-w-6xl lg:px-8 mx-auto px-4 text-red-800">
<svg
class="animate-bounce mt-10 h-16 w-16 mx-auto"
fill="none"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z"
/>
</svg>
<p class="mb-5">
We could not find a wallet in your browser.<br/>
This is probably due to the recent changes made to the framework.<br/>
Please create a new wallet from this browser and try again,
</p>
</div>
{:else}
<div class="text-center max-w-6xl lg:px-8 mx-auto px-4 text-blue-600">
<svg
class="y-10 h-16 w-16 mx-auto mb-3"
fill="none"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z"
/>
</svg>
We could not find a wallet in your browser.<br/>
For now, creating a new wallet while a Web App<br/>
is authenticating, is not implemented. Please<br/>
create or import your wallet in a new tab by<br/><a href="https://nextgraph.eu" target="_blank">clicking here</a>
</div>
{/if}
{:else if $web_origin}
<Home />
{:else}
<div class="text-center max-w-6xl lg:px-8 mx-auto px-4 text-red-800">
<svg
class="animate-bounce mt-10 h-16 w-16 mx-auto"
fill="none"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z"
/>
</svg>
<p class="mb-5">
{@html $t("auth.unexpected_error")}
</p>
</div>
{/if}

@ -1,53 +0,0 @@
// 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.
import {
writable,
readable,
readonly,
derived,
get,
type Writable,
} from "svelte/store";
import { createAsyncProxy } from "async-proxy";
import { RemoteReadableStream } from 'remote-web-streams';
export const selected_broker = writable<undefined | Object>( undefined );
export const brokers_info = writable( {} );
export const unlocked_wallet = writable(undefined);
export const web_origin = writable("");
import worker_ from "./worker.js?worker&inline";
const worker = new worker_();
async function rpc( method:string, args?: any) : Promise<any> {
const { readable, writablePort } = new RemoteReadableStream();
worker.postMessage({ method, args, port: writablePort }, [writablePort]);
const reader = readable.getReader();
let ret = await reader.read();
await reader.read(); // the close.
return ret.value;
}
const handler = {
async apply(_target: object, path: PropertyKey[], _caller: any, args?: any) :Promise<any> {
if (path[0] === "login") {
} else {
return await rpc(<string>path[0], args);
}
}
};
export const ng = createAsyncProxy({}, handler);

@ -1,2 +1 @@
/// <reference types="svelte" />
/// <reference types="vite/client" /> /// <reference types="vite/client" />

@ -1,26 +0,0 @@
// 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.
//import * as sdk from "@ng-org/wasm-tools-auth";
import { fromWritablePort } from 'remote-web-streams';
self.onmessage = (event) => {
(async function() {
const { method, args, port } = event.data;
const writable = fromWritablePort(port);
const writer = writable.getWriter();
console.log("Message received by worker", method, args);
let ret = await Reflect.apply(sdk[method], null, args);
writer.write(ret);
writer.close();
})();
}
console.log("worker loaded");

@ -1,70 +1,14 @@
import { defineConfig } from 'vite' import { defineConfig } from 'vite'
import { svelte, vitePreprocess } from '@sveltejs/vite-plugin-svelte'
import sveltePreprocess from "svelte-preprocess";
import svelteSVG from "vite-plugin-svelte-svg";
import wasm from "vite-plugin-wasm";
import topLevelAwait from "vite-plugin-top-level-await";
import { viteSingleFile } from "vite-plugin-singlefile" import { viteSingleFile } from "vite-plugin-singlefile"
const jsToBottom = () => {
return {
name: "script-at-end-of-body",
transformIndexHtml(html) {
let scriptTag = html.match(/<script type[^>]*>(.*?)<\/script[^>]*>/)[0]
//console.log("\n SCRIPT TAG", scriptTag, "\n")
html = html.replace(scriptTag, "")
html = html.replace("<!-- # INSERT SCRIPT HERE -->", scriptTag)
return html;
}
}
}
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig({
envPrefix: ["VITE_", "NG_"], envPrefix: ["VITE_", "NG_"],
server: { server: {
port: 14402 port: 14404
}, },
worker: {
format: 'es',
plugins : [
topLevelAwait(),
wasm(),
viteSingleFile()
]
},
plugins: [ plugins: [
topLevelAwait(), viteSingleFile()
wasm(),
svelte({
preprocess: [
vitePreprocess(),
sveltePreprocess({
typescript: false,
postcss: true,
}),
],
}),
svelteSVG({
svgoConfig: {
plugins: [
{
name: 'preset-default',
params: {
overrides: {
// disable plugins
removeViewBox: false,
},
},
},
{
name: 'prefixIds',
}
],
}, // See https://github.com/svg/svgo#configuration
requireSuffix: true, // Set false to accept '.svg' without the '?component'
}),
viteSingleFile(),
jsToBottom(),
] ]
}) })

@ -17,6 +17,8 @@
</head> </head>
<body> <body>
<div id="app">
</div>
<script type="module" src="/src/main.ts"></script> <script type="module" src="/src/main.ts"></script>
</body> </body>
</html> </html>

@ -11,13 +11,23 @@
}, },
"dependencies": { "dependencies": {
"private-ip": "^3.0.2", "private-ip": "^3.0.2",
"remote-web-streams": "^0.2.0" "svelte-spa-router": "^3.3.0",
"@ng-org/ui-common": "workspace:*"
}, },
"devDependencies": { "devDependencies": {
"shx": "^0.3.4", "autoprefixer": "^10.4.14",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"shx": "^0.3.4",
"vite": "^4.3.9", "vite": "^4.3.9",
"autoprefixer": "^10.4.14", "@sveltejs/vite-plugin-svelte": "^2.0.4",
"svelte": "^3.58.0",
"postcss": "^8.4.23",
"postcss-load-config": "^4.0.1",
"svelte-heros-v2": "^0.10.12",
"svelte-preprocess": "^5.0.3",
"tailwindcss": "^3.3.1",
"vite-plugin-svelte-svg": "^2.2.1",
"vite-plugin-top-level-await": "1.3.1",
"vite-plugin-singlefile": "0.13.5" "vite-plugin-singlefile": "0.13.5"
} }
} }

@ -0,0 +1,13 @@
const tailwindcss = require("tailwindcss");
const autoprefixer = require("autoprefixer");
const config = {
plugins: [
//Some plugins, like tailwindcss/nesting, need to run before Tailwind,
tailwindcss(),
//But others, like autoprefixer, need to run after,
autoprefixer,
],
};
module.exports = config;

@ -0,0 +1,180 @@
<!--
// 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 { push, default as Router, querystring } from "svelte-spa-router";
import { onMount, tick, onDestroy } from "svelte";
import {
NotFound,
} from "@ng-org/ui-common/routes";
import {error} from "./store";
import Home from "./routes/Home.svelte";
import Error from "./routes/Error.svelte";
import is_ip_private from 'private-ip';
function base64UrlDecode(str) {
const padding = '='.repeat((4 - str.length % 4) % 4);
const base64 = str.replace(/-/g, '+').replace(/_/g, '/') + padding;
return atob(base64);
}
const routes = new Map();
routes.set("/", Home);
routes.set("/error", Error);
routes.set("*", NotFound);
// TODO: take this list from local API
const bsp_list = [
"https://nextgraph.eu",
"https://nextgraph.one",
"https://stage1.nextgraph.eu",
];
let channel;
try {
channel = new BroadcastChannel("ng_bootstrap");
channel.onmessage = (event) => {
if (event.origin !== location.origin) return;
if (!event.data.key) return;
(async () => {
try {
let bootstraps = JSON.parse(localStorage.getItem("ng_bootstrap") || "{}");
if (event.data.value){
//console.log("received added",event.data.key, event.data.value);
if(!bootstraps[event.data.key]) {
bootstraps[event.data.key] = event.data.value;
localStorage.setItem("ng_bootstrap",JSON.stringify(bootstraps));
}
} else {
//console.log("received removed", event.data.key);
if ( bootstraps[event.data.key]) {
delete bootstraps[event.data.key];
localStorage.setItem("ng_bootstrap",JSON.stringify(bootstraps));
}
}
} catch (e) {
console.log("localStorage error in BroadcastChannel",e)
}
})();
}
}
catch (e) {
console.error("error in BroadcastChannel",e)
}
onMount(() => {
const param = new URLSearchParams($querystring);
let method = param.get("m");
let url;
let msgs;
try {
url = new URL(decodeURIComponent(param.get("ab")));
msgs = JSON.parse(base64UrlDecode(param.get("b")));
if (!method)
throw new Error("InvalidValue");
}
catch (e) {
console.error(e);
error.set(e);
push("#/error");
return;
}
let origin_url = url.origin;
let hostname = url.hostname;
let is_ng_box = origin_url === "https://nextgraph.app";
let is_domain = false;
let is_lan = false;
let is_local = false;
if (!is_ng_box) {
is_local = origin_url.startsWith("http://localhost");
if (!is_local) {
is_lan = !!is_ip_private(hostname);
if (!is_lan)
is_domain = bsp_list.includes(origin_url);
}
}
function abort(error) {
console.error(error);
let u = url.toString();
window.location.href = u + "&re=" + error;
throw new Error(error);
}
try {
let keys: Array<string> = [];
for (const data of msgs) {
let key;
//console.log("ng_bootstrap received msg",JSON.stringify(data), is_ng_box, is_domain,is_lan,is_local,new URL(origin_url).hostname, new URL(origin_url).hostname === data.domain, data.domain && is_domain && new URL(origin_url).hostname === data.domain )
if (data.ngbox && (is_ng_box || is_lan || is_local || is_domain)) {
key = "Self-hosted / NGbox";
} else if (data.domain && is_domain && new URL(origin_url).hostname === data.domain ) {
key = data.domain;
//console.log("key for domain is", key)
} else if (data.localhost && (is_local || is_lan)) {
if (!data.peer_id) {
abort("missing peer_id of localhost");
}
let port = Number(new URL(origin_url).port || '80');
if (!import.meta.env.NG_DEV && !import.meta.env.DEV && is_local && port !== data.localhost) {
abort("mismatch of localhost port");
}
key = `Local port ${data.localhost}`;
} else if (data.private && (is_lan || is_local )) {
if (!data.peer_id) {
abort("missing peer_id of LAN");
}
key = `Network ${data.peer_id.substring(0,7)}`;
} else {
abort("mismatch between origin and msg");
}
keys.push(key);
}
try {
let bootstraps = JSON.parse(localStorage.getItem("ng_bootstrap") || "{}");
for (const [i, key] of keys.entries()) {
//console.log(method, method === "add",bootstraps, !bootstraps[key])
const value = msgs[i];
if ( method === "add" && !bootstraps[key]) {
//console.log("adding..."+key)
bootstraps[key] = value;
if (channel) channel.postMessage({ key, value });
//console.log("added",key,value);
} else if ( method === "remove" && bootstraps[key]) {
delete bootstraps[key];
if (channel) channel.postMessage({ key });
//console.log("removed",key);
}
}
localStorage.setItem("ng_bootstrap",JSON.stringify(bootstraps));
} catch (e) {
abort("NoLocalStorage");
}
let u = url.toString();
// url.searchParams.set('i', param.get("i"));
// url.searchParams.set('rs', param.get("rs"));
// url.searchParams.set('ab', "1");
window.location.href = u + "&ab=1";
}catch {}
});
</script>
<Router {routes} />

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

@ -7,200 +7,12 @@
// notice may not be copied, modified, or distributed except // notice may not be copied, modified, or distributed except
// according to those terms. // according to those terms.
import is_ip_private from 'private-ip'; import "./app.postcss";
import "../../../../app/ui-common/src/styles.css";
import App from "./App.svelte";
import { fromWritablePort } from 'remote-web-streams'; const app = new App({
target: document.getElementById("app"),
});
// TODO: take this list from local API export default app;
const bsp_list = [
"https://nextgraph.eu",
"https://nextgraph.one",
];
let channel;
try {
channel = new BroadcastChannel("ng_bootstrap");
channel.onmessage = (event) => {
if (event.origin !== location.origin) return;
if (!event.data.key) return;
(async () => {
try {
let ls = localStorage;
try {
let ret = await document.requestStorageAccess({ localStorage: true });
ls = ret.localStorage;
console.log("BroadcastChannel: REQUEST STORAGE ACCESS GRANTED by chrome");
}
catch(e) {
console.warn("BroadcastChannel: requestStorageAccess of chrome failed. falling back to previous api", e)
try {
await document.requestStorageAccess();
console.log("BroadcastChannel: REQUEST STORAGE ACCESS GRANTED");
} catch (e) {
console.error("BroadcastChannel: REQUEST STORAGE ACCESS DENIED",e);
return;
}
}
let bootstraps = JSON.parse(ls.getItem("ng_bootstrap") || "{}");
if (event.data.value){
console.log("received added",event.data.key, event.data.value);
if(!bootstraps[event.data.key]) {
bootstraps[event.data.key] = event.data.value;
ls.setItem("ng_bootstrap",JSON.stringify(bootstraps));
}
} else {
console.log("received removed", event.data.key);
if ( bootstraps[event.data.key]) {
delete bootstraps[event.data.key];
ls.setItem("ng_bootstrap",JSON.stringify(bootstraps));
}
}
} catch (e) {
console.log("localStorage error in BroadcastChannel",e)
}
})();
}
}
catch (e) {
console.error("error in BroadcastChannel",e)
}
window.addEventListener("message", async (event)=>{
//console.log("net-bootstrap got msg", event.data, event.origin)
const {method, port, msgs} = event.data;
const writable = fromWritablePort(port);
const writer = writable.getWriter();
if (method === "test") {
try {
let ls = localStorage;
try {
let ret = await document.requestStorageAccess({ localStorage: true });
ls = ret.localStorage;
console.log("REQUEST STORAGE ACCESS GRANTED by chrome");
}
catch(e) {
console.warn("requestStorageAccess of chrome failed. falling back to previous api", e)
try {
await document.requestStorageAccess();
console.log("REQUEST STORAGE ACCESS GRANTED");
} catch (e) {
console.error("REQUEST STORAGE ACCESS DENIED",e);
writer.write({status:'error', error:e});
writer.close();
return;
}
}
localStorage
//console.log("net-bootstrap writes back ok")
writer.write({status:'ok'});
//console.log(`localStorage on bootstrap ${location.origin} is ok`)
} catch (e) {
console.log("net-bootstrap writes back error")
writer.write({status:'error', error:e});
console.error(`localStorage on bootstrap ${location.origin} is blocked`, e)
}
writer.close();
return;
}
let is_ng_box = event.origin === "https://nextgraph.app";
let is_domain = false;
let is_lan = false;
let is_local = false;
if (!is_ng_box) {
is_local = event.origin.startsWith("http://localhost");
if (!is_local) {
is_lan = !!is_ip_private(new URL(event.origin).hostname);
if (!is_lan)
is_domain = bsp_list.includes(event.origin);
}
}
let keys: Array<string> = [];
for (const data of msgs) {
let key;
//console.log("ng_bootstrap received msg",JSON.stringify(data), is_ng_box, is_domain,is_lan,is_local,new URL(event.origin).hostname, new URL(event.origin).hostname === data.domain, data.domain && is_domain && new URL(event.origin).hostname === data.domain )
if (data.ngbox && (is_ng_box || is_lan || is_local || is_domain)) {
key = "Self-hosted / NGbox";
} else if (data.domain && is_domain && new URL(event.origin).hostname === data.domain ) {
key = data.domain;
//console.log("key for domain is", key)
} else if (data.localhost && (is_local || is_lan)) {
if (!data.peer_id) {
writer.write({status:'error', error:"missing peer_id of localhost"});
writer.close();
return;
}
let port = Number(new URL(event.origin).port || '80');
if (!import.meta.env.NG_DEV && is_local && port !== data.localhost) {
writer.write({status:'error', error:"mismatch of localhost port"});
writer.close();
return;
}
key = `Local port ${data.localhost}`;
} else if (data.private && (is_lan || is_local )) {
if (!data.peer_id) {
writer.write({status:'error', error:"missing peer_id of LAN"});
writer.close();
return;
}
key = `Network ${data.peer_id.substring(0,7)}`;
} else {
writer.write({status:'error', error:"mismatch between origin and msg"});
writer.close();
return;
}
keys.push(key);
}
try {
let ls = localStorage;
try {
let ret = await document.requestStorageAccess({ localStorage: true });
ls = ret.localStorage;
console.log("REQUEST STORAGE ACCESS GRANTED by chrome");
}
catch(e) {
console.warn("requestStorageAccess of chrome failed. falling back to previous api", e)
try {
await document.requestStorageAccess();
console.log("REQUEST STORAGE ACCESS GRANTED");
} catch (e) {
console.error("REQUEST STORAGE ACCESS DENIED",e);
writer.write({status:'error', error:`REQUEST STORAGE ACCESS DENIED : ${e}`});
writer.close();
return;
}
}
let bootstraps = JSON.parse(ls.getItem("ng_bootstrap") || "{}");
for (const [i, key] of keys.entries()) {
//console.log(method, method === "add",bootstraps, !bootstraps[key])
const value = msgs[i];
if ( method === "add" && !bootstraps[key]) {
//console.log("adding..."+key)
bootstraps[key] = value;
if (channel) channel.postMessage({ key, value });
//console.log("added",key,value);
} else if ( method === "remove" && bootstraps[key]) {
delete bootstraps[key];
if (channel) channel.postMessage({ key });
//console.log("removed",key);
}
}
ls.setItem("ng_bootstrap",JSON.stringify(bootstraps));
writer.write({status:'ok'});
writer.close();
}
catch (e) {
console.error("access to local Storage for nextgraph.net is blocked")
writer.write({status:'error', error:`access to local Storage for nextgraph.net is blocked : ${e}`});
writer.close();
return;
}
}, false);

@ -0,0 +1,28 @@
<!--
// 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.
-->
<!--
Error page (?o=https://domain.tld was not provided or is invalid)
-->
<script>
import { ExclamationTriangle} from "svelte-heros-v2";
import {error} from "../store";
</script>
<div class="text-center max-w-6xl lg:px-8 mx-auto px-4 text-green-800">
<div class=" max-w-6xl lg:px-8 mx-auto px-4 text-red-800">
<ExclamationTriangle class="animate-bounce mt-10 h-16 w-16 mx-auto" />
<p class="max-w-xl md:mx-auto lg:max-w-2xl mb-5">
{$error}
</p>
</div>
</div>

@ -0,0 +1,20 @@
<!--
// 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>
import Logo from "../assets/nextgraph.svg?component";
</script>
<a href="https://nextgraph.org">
<Logo style="margin:auto;" class="logo block h-40" alt="NextGraph" />
</a>

@ -0,0 +1,15 @@
// 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.
import {
writable,
type Writable,
} from "svelte/store";
export const error = writable("");

@ -3,7 +3,6 @@ const defaultTheme = require('tailwindcss/defaultTheme')
const config = { const config = {
content: [ content: [
"./src/**/*.{html,js,svelte,ts}", "./src/**/*.{html,js,svelte,ts}",
"./node_modules/flowbite-svelte/**/*.{html,js,svelte,ts}",
"./node_modules/@nng-org/ui-common/src/**/*.{html,js,svelte,ts}", "./node_modules/@nng-org/ui-common/src/**/*.{html,js,svelte,ts}",
], ],
theme: { theme: {

@ -1,4 +1,7 @@
import { defineConfig } from 'vite' import { defineConfig } from 'vite'
import { svelte, vitePreprocess } from '@sveltejs/vite-plugin-svelte'
import sveltePreprocess from "svelte-preprocess";
import svelteSVG from "vite-plugin-svelte-svg";
import { viteSingleFile } from "vite-plugin-singlefile" import { viteSingleFile } from "vite-plugin-singlefile"
// https://vitejs.dev/config/ // https://vitejs.dev/config/
@ -7,13 +10,35 @@ export default defineConfig({
server: { server: {
port: 14403 port: 14403
}, },
worker: {
format: 'es',
plugins : [
viteSingleFile()
]
},
plugins: [ plugins: [
viteSingleFile(), svelte({
preprocess: [
vitePreprocess(),
sveltePreprocess({
typescript: false,
postcss: true,
}),
],
}),
svelteSVG({
svgoConfig: {
plugins: [
{
name: 'preset-default',
params: {
overrides: {
// disable plugins
removeViewBox: false,
},
},
},
{
name: 'prefixIds',
}
],
}, // See https://github.com/svg/svgo#configuration
requireSuffix: true, // Set false to accept '.svg' without the '?component'
}),
viteSingleFile()
] ]
}) })

@ -13,7 +13,7 @@
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>NextGraph Auth</title> <title>NextGraph.net Auth Redirect</title>
<style> <style>
.splashing { .splashing {
height: 95vh; height: 95vh;
@ -25,41 +25,10 @@
.noshow { .noshow {
display: none !important; display: none !important;
} }
.nextgraph-app-auth-iframe {
visibility: hidden;
}
.nextgraph-app-auth-iframe.nextgraph-app-auth-iframe--active {
visibility: visible;
}
#banner {
padding-right: 36px !important;
width: 100%; position: fixed; left:0; top:0; min-height:36px; background-color: rgb(73, 114, 165); color: white; text-align:center ;z-index:10; padding:3px; font-size: 1.25rem;
line-height: 1.75rem; overflow-wrap: break-word;
}
#close-auth {
position: fixed; right:0; top:0; width: 36px; height: 36px; background-color: rgb(73, 114, 165);z-index:11;
cursor:pointer;
}
</style> </style>
</head> </head>
<body> <body>
<iframe id="nextgraph-app-auth-iframe" class="nextgraph-app-auth-iframe" scrolling="auto" frameborder="0"
style="position: fixed; left: 0; top: 0; height: 100%; width: 100%; overflow:auto;">
</iframe>
<div id="banner">
</div>
<div id="close-auth">
<svg data-slot="icon" fill="none" stroke-width="1.5" stroke="white" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12"></path>
</svg>
</div>
<script>
document.getElementById("close-auth").onclick = (e)=> {
window.ng_status_callback.write({status:"cancelled"});
};
</script>
<div id="splash" class="splashing"> <div id="splash" class="splashing">
<div style="flex-direction: column;justify-content: center;color:#4972a5;width:100%;text-align:center;font-family: Inter, Avenir, Helvetica, Arial, sans-serif;"> <div style="flex-direction: column;justify-content: center;color:#4972a5;width:100%;text-align:center;font-family: Inter, Avenir, Helvetica, Arial, sans-serif;">
<svg <svg

@ -6,7 +6,7 @@
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "vite build --base=./ && shx rm -rf ./dist/assets", "build": "vite build --base=./ && shx rm -rf ./dist/assets",
"builddev": "cross-env NG_DEV=1 vite build --base=./ && shx rm -rf ./dist/assets && shx mkdir -p ../../../app/nextgraph/public_dev && cp ./dist/index.html ../../../app/nextrgaph/public_dev/redir.html", "builddev": "cross-env NG_DEV=1 vite build --base=./ && shx rm -rf ./dist/assets && shx mkdir -p ../../../app/nextgraph/public_dev && cp ./dist/index.html ../../../app/nextgraph/public_dev/redir.html",
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
@ -15,9 +15,7 @@
"svelte-spa-router": "^3.3.0", "svelte-spa-router": "^3.3.0",
"@tailwindcss/typography": "^0.5.13", "@tailwindcss/typography": "^0.5.13",
"svelte-i18n": "^4.0.0", "svelte-i18n": "^4.0.0",
"@ng-org/ui-common": "workspace:*", "@ng-org/ui-common": "workspace:*"
"async-proxy": "^0.4.1",
"remote-web-streams": "^0.2.0"
}, },
"devDependencies": { "devDependencies": {
"shx": "^0.3.4", "shx": "^0.3.4",

@ -1,32 +0,0 @@
var crypto = require('crypto')
, fs = require('fs')
const {gzip, } = require('node-gzip');
var algorithm = 'sha256'
, shasum = crypto.createHash(algorithm)
const sha_file = './dist/index.sha256';
const gzip_file = './dist/index.gzip';
var filename = './dist/index.html'
, s = fs.ReadStream(filename)
var bufs = [];
s.on('data', function(data) {
shasum.update(data)
bufs.push(data);
})
s.on('end', function() {
var hash = shasum.digest('hex')
console.log(hash + ' ' + filename)
fs.writeFileSync(sha_file, hash, 'utf8');
var buf = Buffer.concat(bufs);
gzip(buf).then((compressed) => {fs.writeFileSync(gzip_file, compressed);});
fs.rm(filename,()=>{});
})

@ -10,73 +10,45 @@
--> -->
<script lang="ts"> <script lang="ts">
import { push, default as Router } from "svelte-spa-router"; import { push, default as Router, querystring } from "svelte-spa-router";
import { isLoading } from "svelte-i18n"; import { isLoading } from "svelte-i18n";
import { onMount, tick, onDestroy } from "svelte"; import { onMount, tick, onDestroy } from "svelte";
import { ng, brokers_info } from "./store"; import { brokers_info, web_origin, host } from "./store";
import { import {
NotFound, NotFound,
Test,
WalletCreate,
Invitation,
WalletLogin,
WalletInfo,
User,
UserRegistered,
Install,
ScanQRWeb,
AccountInfo,
WalletLoginUsername,
WalletLoginQr,
WalletLoginTextCode
} from "@ng-org/ui-common/routes"; } from "@ng-org/ui-common/routes";
import { Bowser } from "../../../../sdk/js/lib-wasm/jsland/bowser.js";
import Home from "./routes/Home.svelte"; import Home from "./routes/Home.svelte";
const routes = new Map(); const routes = new Map();
routes.set("/", Home); routes.set("/", Home);
// routes.set("/test", Test);
// routes.set("/wallet/login", WalletLogin);
// routes.set("/wallet/username", WalletLoginUsername);
// routes.set("/wallet/login-qr", WalletLoginQr);
// routes.set("/wallet/login-text-code", WalletLoginTextCode);
// routes.set("/wallet/create", WalletCreate);
// routes.set("/i/:invitation", Invitation);
// routes.set("/user", User);
// routes.set("/user/registered", UserRegistered);
// routes.set("/wallet", WalletInfo);
// routes.set("/user/accounts", AccountInfo);
// routes.set("/scanqr", ScanQRWeb);
// routes.set("/install", Install);
routes.set("*", NotFound); routes.set("*", NotFound);
// window.refresh_wallets = async () => {
// let walls = await ng.get_wallets();
// wallets.set(walls);
// };
let no_local_storage = false;
let is_safari = false;
function load_bootstraps(bs: string | null) { function load_bootstraps(bs: string | null) {
if (bs) { if (bs) {
let bootstrap_map = JSON.parse(bs); let bootstrap_map = JSON.parse(bs);
brokers_info.set(bootstrap_map); brokers_info.set(bootstrap_map);
//console.log(bootstrap_map);
} }
} }
const param = new URLSearchParams($querystring);
let origin_url = decodeURIComponent(param.get("o"));
try {
let host_ = new URL(origin_url).host;
//console.log(host_);
web_origin.set(origin_url);
host.set(host_);
} catch {
}
onMount(async () => { onMount(async () => {
window.document.getElementById("splash").className="noshow"; window.document.getElementById("splash").className="noshow";
window.document.getElementById("app").className=""; window.document.getElementById("app").className="";
let info = Bowser.parse(window.navigator.userAgent);
//console.log(info);
is_safari = info.browser.name == "Safari";
if (is_safari) return;
window.addEventListener("storage", (event) => { window.addEventListener("storage", (event) => {
//console.log("localStorage event", event); //console.log("localStorage event", event);
if (event.storageArea != localStorage) return; if (event.storageArea != localStorage) return;
@ -85,96 +57,21 @@
} }
}); });
let ls;
try {
ls = localStorage;
try {
let ret = await document.requestStorageAccess({ localStorage: true });
ls = ret.localStorage;
console.log("REQUEST STORAGE ACCESS GRANTED by chrome");
}
catch(e) {
console.warn("requestStorageAccess of chrome failed. falling back to previous api", e)
try {
await document.requestStorageAccess();
localStorage;
console.log("REQUEST STORAGE ACCESS GRANTED");
} catch (e) {
console.error("REQUEST STORAGE ACCESS DENIED",e);
no_local_storage = true;
}
}
} catch (e) {
no_local_storage = true;
console.log("no access to localStorage",e)
}
if (!no_local_storage) {
try { try {
load_bootstraps(ls.getItem("ng_bootstrap")); load_bootstraps(localStorage.getItem("ng_bootstrap"));
} catch (e) { } catch (e) {
console.log("load_bootstraps failed") console.log("load_bootstraps failed", e)
} }
}
}); });
</script> </script>
{#if is_safari}
<div class="text-center max-w-6xl lg:px-8 mx-auto px-4 text-red-800">
<svg
class="animate-bounce mt-10 h-16 w-16 mx-auto"
fill="none"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z"
/>
</svg>
<p class="mb-5">
We are sorry but Safari is not supported yet<br/>for WebApps authentication with your Wallet.<br/>Please use another browser.
</p>
</div>
{:else if no_local_storage}
<div class="text-center max-w-6xl lg:px-8 mx-auto px-4 text-red-800">
<svg
class="animate-bounce mt-10 h-16 w-16 mx-auto"
fill="none"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z"
/>
</svg>
<p class="mb-5">
Please give access to localStorage for the website<br/>
{location.origin}
</p>
</div>
{#if $isLoading}
<p class="text-center">Loading translations...</p>
{:else} {:else}
<Router {routes} />
{#if $isLoading}
<p class="text-center">Loading translations...</p>
{:else}
<Router {routes} />
{/if}
{/if} {/if}

@ -1,4 +0,0 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg viewBox="0 0 810 540" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" ><desc>European flag</desc>
<defs><g id="s"><g id="c"><path id="t" d="M0,0v1h0.5z" transform="translate(0,-1)rotate(18)"/><use xlink:href="#t" transform="scale(-1,1)"/></g><g id="a"><use xlink:href="#c" transform="rotate(72)"/><use xlink:href="#c" transform="rotate(144)"/></g><use xlink:href="#a" transform="scale(-1,1)"/></g></defs>
<rect fill="#039" width="810" height="540"/><g fill="#fc0" transform="scale(30)translate(13.5,9)"><use xlink:href="#s" y="-6"/><use xlink:href="#s" y="6"/><g id="l"><use xlink:href="#s" x="-6"/><use xlink:href="#s" transform="rotate(150)translate(0,6)rotate(66)"/><use xlink:href="#s" transform="rotate(120)translate(0,6)rotate(24)"/><use xlink:href="#s" transform="rotate(60)translate(0,6)rotate(12)"/><use xlink:href="#s" transform="rotate(30)translate(0,6)rotate(42)"/></g><use xlink:href="#l" transform="scale(-1,1)"/></g>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 225 225"
>
<g>
<circle
r="106.98013"
cy="112.90476"
cx="109.88096"
style="fill:#ffffff;stroke:none;stroke-width:0.268375" />
<path
d="M 98.343352,190.26108 C 80.403778,187.53354 65.011938,179.57839 52.608228,166.62327 38.602093,151.99448 31.178059,133.41381 31.178059,112.98841 c 0,-10.21889 1.700058,-19.44396 5.221234,-28.332119 4.28678,-10.820699 10.037295,-19.39063 18.535095,-27.62263 4.72982,-4.58187 6.60687,-6.10643 11.28099,-9.16256 11.89869,-7.779841 24.173884,-11.879991 38.095802,-12.724761 19.80437,-1.2017 39.11165,5.11306 54.60284,17.858751 1.50718,1.24006 2.72951,2.35934 2.71628,2.48729 -0.0132,0.12795 -3.85821,3.63443 -8.54442,7.79217 -4.6862,4.157729 -10.04724,8.96276 -11.91342,10.677819 -1.86617,1.715071 -3.54094,3.11831 -3.7217,3.11831 -0.18075,0 -1.39985,-0.745188 -2.70911,-1.655969 -7.53011,-5.23834 -15.99428,-7.82188 -25.62597,-7.82188 -12.731628,0 -23.249192,4.3379 -32.143882,13.257541 -6.39594,6.413868 -10.70387,14.555268 -12.50018,23.623578 -0.69099,3.48832 -0.68968,13.53072 0.002,17.00893 3.70508,18.62577 18.31886,33.10194 36.642322,36.29729 4.16439,0.72621 11.98099,0.71223 15.98975,-0.0286 14.03187,-2.59311 25.86047,-11.36806 32.26533,-23.93578 0.77379,-1.51834 1.26018,-2.88461 1.08086,-3.03616 -0.17934,-0.15156 -6.87448,-1.1779 -14.87813,-2.28078 -9.7795,-1.34758 -14.92353,-2.21379 -15.68471,-2.64117 -1.52067,-0.85379 -2.83611,-2.88806 -2.83611,-4.3859 0,-1.1732 2.02687,-15.86876 2.49085,-18.05962 0.29676,-1.40127 2.42559,-3.4934 3.84317,-3.77691 0.62227,-0.12445 8.82712,0.85555 18.28065,2.18348 9.43343,1.32511 17.26269,2.29453 17.39833,2.15427 0.13566,-0.14026 1.11808,-6.54833 2.18313,-14.24014 1.10778,-8.000208 2.20407,-14.60184 2.56177,-15.426229 0.34392,-0.792599 1.11019,-1.849131 1.70287,-2.34782 2.06321,-1.736079 3.1433,-1.785011 12.20439,-0.55291 9.63637,1.310309 10.70873,1.56224 12.28077,2.88503 1.64359,1.382979 2.2732,2.810909 2.25906,5.123309 -0.007,1.10173 -0.92172,8.29645 -2.03332,15.98826 -1.11158,7.69182 -1.97159,14.04091 -1.91113,14.1091 0.0605,0.0682 7.16644,1.11143 15.79109,2.31832 11.10566,1.55407 16.00827,2.38757 16.80223,2.85657 1.53015,0.90389 2.48023,2.64785 2.45017,4.49756 -0.0462,2.84349 -2.41252,18.12279 -2.97521,19.21089 -0.66164,1.27949 -2.60244,2.54696 -3.92109,2.56074 -0.51973,0.005 -7.87449,-0.95937 -16.34391,-2.144 -8.46944,-1.18464 -15.47588,-2.077 -15.56986,-1.98301 -0.094,0.094 -1.18792,7.34163 -2.43097,16.10589 -1.44004,10.15311 -2.49792,16.43621 -2.91556,17.31631 -0.72531,1.52848 -2.76261,3.06291 -4.53817,3.41802 -0.95688,0.19138 -10.90014,-0.92798 -13.59859,-1.53084 -0.5471,-0.12223 -1.89146,0.67252 -4.50941,2.66588 -11.2627,8.57562 -24.34195,13.90917 -38.35741,15.64164 -4.40038,0.54395 -15.72658,0.43298 -19.853658,-0.19451 z"
style="fill:#4972a5;fill-opacity:1;stroke:#4972a5;stroke-width:0.377976;stroke-opacity:1" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.9 KiB

@ -9,10 +9,11 @@
// according to those terms. // according to those terms.
--> -->
<script lang="ts"> <script lang="ts">
import { onMount } from "svelte"; import { onMount, tick } from "svelte";
import { t, locale } from "svelte-i18n"; import { t, locale } from "svelte-i18n";
import { CenteredLayout } from "@ng-org/ui-common/lib"; import { CenteredLayout } from "@ng-org/ui-common/lib";
import { LogoSimple } from "@ng-org/ui-common/components"; import { LogoSimple } from "@ng-org/ui-common/components";
import { push, default as Router, querystring } from "svelte-spa-router";
import { import {
Sidebar, Sidebar,
SidebarGroup, SidebarGroup,
@ -25,19 +26,23 @@
ServerStack ServerStack
} from "svelte-heros-v2"; } from "svelte-heros-v2";
import { web_origin, brokers_info, selected_broker } from '../store'; import { web_origin, host, brokers_info, selected_broker } from '../store';
import { fromWritablePort, RemoteReadableStream } from 'remote-web-streams';
let top; let top;
let nonActiveClass = let nonActiveClass =
"flex items-center p-2 text-base font-normal text-gray-900 rounded-lg dark:text-white hover:bg-gray-200 dark:hover:bg-gray-700"; "flex items-center p-2 text-base font-normal text-gray-900 rounded-lg dark:text-white hover:bg-gray-200 dark:hover:bg-gray-700";
const AUTH_HOME = "#/"; let redirecting = false;
let broker_name = "";
function select(broker: Object) {
async function select(broker_info) {
let broker = broker_info[1];
broker_name = broker_info[0];
let url; let url;
if (import.meta.env.NG_DEV && broker.localhost === 14400) { if (import.meta.env.DEV && broker.localhost === 1421) {
// dev mode
url = "http://localhost:14401/";
} else if (import.meta.env.NG_DEV && broker.localhost === 14400) {
// dev mode // dev mode
url = "http://localhost:1421/appauth.html"; url = "http://localhost:1421/appauth.html";
} else if (broker.localhost) { } else if (broker.localhost) {
@ -52,52 +57,45 @@
} else return; } else return;
selected_broker.set(broker); selected_broker.set(broker);
let iframe = window.document.getElementById("nextgraph-app-auth-iframe");
iframe?.classList.add('nextgraph-app-auth-iframe--active');
window.document.getElementById("app").style["display"] = "none";
(<any>window).ng_iframe_origin = new URL(url).origin;
iframe.addEventListener("load", function() {
(<any>window).ng_broker_selected = this.contentWindow;
const ready_handler = async function(m) {
if (m.data.ready && m.origin === (<any>window).ng_iframe_origin) {
//console.log("got ready message",m);
//remove this listener
window.removeEventListener("message",ready_handler);
const { readable, writablePort } = new RemoteReadableStream();
//console.log("sending init message to app-auth");
(<any>window).ng_broker_selected.postMessage({ method: "init", manifest:window.ng_manifest, port: writablePort }, (<any>window).ng_iframe_origin, [writablePort]);
const reader = readable.getReader();
for (var msg; msg = await reader.read(); ) {
if (msg.done) {
(<any>window).ng_status_callback.close();
break;
} else {
//console.log("forwarding upstream",msg.value);
(<any>window).ng_status_callback.write(msg.value);
}
}
}
};
window.addEventListener("message",ready_handler);
});
iframe.src = url+"?o="+location.search.substring(3)+AUTH_HOME; redirecting = true;
await tick();
let encoded_origin = encodeURIComponent($web_origin);
window.location.href = url+"#/?o="+encoded_origin;
} }
onMount(() => { onMount(() => {
if (Object.keys($brokers_info).length == 1) { if (Object.keys($brokers_info).length == 1) {
select(Object.values($brokers_info)[0]); select(Object.entries($brokers_info)[0]);
} }
}); });
</script> </script>
{#if redirecting}
<div class="text-center max-w-6xl lg:px-8 mx-auto px-4 text-primary-700">
{@html $t("pages.login.redirecting")} your Broker at {broker_name},<br/>for logging in to {$host} ...
<svg
class="my-10 h-16 w-16 mx-auto"
fill="none"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M9 12.75L11.25 15 15 9.75M21 12c0 1.268-.63 2.39-1.593 3.068a3.745 3.745 0 01-1.043 3.296 3.745 3.745 0 01-3.296 1.043A3.745 3.745 0 0112 21c-1.268 0-2.39-.63-3.068-1.593a3.746 3.746 0 01-3.296-1.043 3.745 3.745 0 01-1.043-3.296A3.745 3.745 0 013 12c0-1.268.63-2.39 1.593-3.068a3.745 3.745 0 011.043-3.296 3.746 3.746 0 013.296-1.043A3.746 3.746 0 0112 3c1.268 0 2.39.63 3.068 1.593a3.746 3.746 0 013.296 1.043 3.746 3.746 0 011.043 3.296A3.745 3.745 0 0121 12z"
/>
</svg>
</div>
{:else}
{#if Object.keys($brokers_info).length > 1} {#if Object.keys($brokers_info).length > 1}
<CenteredLayout> <CenteredLayout>
<div class="container3" bind:this={top}> <div class="container3" bind:this={top}>
<div class="row"> <div class="row mb-5">
<LogoSimple/> <LogoSimple/>
</div> </div>
<div class="row mb-20"> <div class="row mb-20">
@ -107,15 +105,15 @@
> >
<SidebarGroup ulClass="space-y-2" role="menu"> <SidebarGroup ulClass="space-y-2" role="menu">
<li> <li>
<h2 class="text-xl mb-6">{@html $t("auth.select_broker", {values: { origin:$web_origin }})}</h2> <h2 class="text-xl mb-6">{@html $t("auth.select_broker", {values: { origin:$host }})}</h2>
</li> </li>
{#each Object.entries($brokers_info) as broker} {#each Object.entries($brokers_info) as broker}
<li <li
tabindex="0" tabindex="0"
role="menuitem" role="menuitem"
class="flex items-center p-2 text-base font-normal text-gray-900 clickable rounded-lg dark:text-white hover:bg-gray-200 dark:hover:bg-gray-700" class="flex items-center p-2 text-base font-normal text-gray-900 clickable rounded-lg dark:text-white hover:bg-gray-200 dark:hover:bg-gray-700"
on:keypress={()=>select(broker[1])} on:keypress={()=>select(broker)}
on:click={()=>select(broker[1])} on:click={()=>select(broker)}
> >
{#if broker[1].localhost} {#if broker[1].localhost}
<ComputerDesktop tabindex="-1" <ComputerDesktop tabindex="-1"
@ -158,3 +156,5 @@
</div> </div>
</CenteredLayout> </CenteredLayout>
{/if} {/if}
{/if}

@ -9,110 +9,11 @@
import "./app.postcss"; import "./app.postcss";
import "../../../../app/ui-common/src/styles.css"; import "../../../../app/ui-common/src/styles.css";
import { link, push } from "svelte-spa-router";
import App from "./App.svelte"; import App from "./App.svelte";
import { fromWritablePort, RemoteReadableStream } from 'remote-web-streams';
import { web_origin } from './store';
import { select_default_lang } from "@ng-org/ui-common/lang"; import { select_default_lang } from "@ng-org/ui-common/lang";
select_default_lang(()=>{return window.navigator.languages;}).then(() => {}); select_default_lang(()=>{return window.navigator.languages;}).then(() => {});
//let status_callback : WritableStreamDefaultWriter<any> | undefined = undefined;
const origin = decodeURIComponent(location.search.substring(3));
document.getElementById("banner").innerText = "Opening Wallet for "+ new URL(origin).host;
async function rpc( method:string, args?: any) : Promise<any> {
const { readable, writablePort } = new RemoteReadableStream();
(<any>window).ng_broker_selected.postMessage({ method, args, port: writablePort }, (<any>window).ng_iframe_origin, [writablePort]);
const reader = readable.getReader();
let ret = await reader.read();
await reader.read(); // the close
return ret.value;
}
async function rpc_stream( method:string, args: any, writer:WritableStreamDefaultWriter<any>) {
const { readable, writablePort } = new RemoteReadableStream();
(<any>window).ng_broker_selected.postMessage({ method, args, port: writablePort }, (<any>window).ng_iframe_origin, [writablePort]);
const reader = readable.getReader();
for (var msg; msg = await reader.read(); ) {
if (msg.done) {
writer.close();
break;
}
if (msg.value.error) {
writer.write(msg.value);
writer.close();
break;
} else if (msg.value.stream) {
writer.write(msg.value);
}
// TODO: deal with end of stream
}
}
const AUTH_HOME = "#/";
// const AUTH_USER_PANEL = "#/user";
// const AUTH_USER_ACCOUNTS = "#/user/accounts";
// const AUTH_WALLET = "#/wallet";
window.addEventListener("message", async (event)=>{
if (event.data.ready) return;
const { method, port } = event.data;
const writable = fromWritablePort(port);
const writer = writable.getWriter();
if (event.origin !== origin) {
console.error("invalid origin",event.origin,origin)
writer.write({status:'error', error:'invalid origin'});
writer.close();
} else if ( method === "init" ) {
(<any>window).ng_status_callback = writer;
web_origin.set(new URL(origin).host);
// make API call with origin, event.data.singleton and event.data.access_requests
// in order to get full manifest (including security info)
(<any>window).ng_manifest = {
origin: origin,
singleton: event.data.singleton,
access_request: event.data.access_requests,
name: "",
title: "",
description: "", // etc...
security_info: {}
};
} else if ( method === "login" ) {
if (!(<any>window).ng_broker_selected) {
push(AUTH_HOME);
writer.write({ok:true, ret: false});
writer.close();
} else {
writer.write(await rpc("login"));
writer.close();
}
} else if ( method === "doc_subscribe" ) {
//console.log("net forward doc_subscribe to app", method, event.data.args)
await rpc_stream(method, event.data.args, writer);
} else {
//console.log("net forward to app", method, event.data.args)
// forward to app auth iframe
writer.write(await rpc(method, event.data.args));
writer.close();
}
}, false);
/// for test purposes only, when testing with http://localhost:14402/?o=http://localhost:14402
// const { readable, writablePort } = new RemoteReadableStream();
// window.postMessage({method:"init", port: writablePort }, location.origin, [writablePort]);
const app = new App({ const app = new App({
target: document.getElementById("app"), target: document.getElementById("app"),
}); });

@ -10,71 +10,67 @@
--> -->
<!-- <!--
Home page to display for logged in users. Home page of ngnet redirect
Redirects to no-wallet or login page, if not logged in.
--> -->
<script> <script>
import Home from "../lib/Home.svelte"; import Home from "../lib/Home.svelte";
import { t, locale } from "svelte-i18n"; import { t, locale } from "svelte-i18n";
import { NoWallet } from "@ng-org/ui-common/lib";
import { push } from "svelte-spa-router";
import { onMount, onDestroy } from "svelte";
import { brokers_info, web_origin } from "../store"; import { brokers_info, web_origin } from "../store";
</script> </script>
{#if $web_origin}
{#if Object.keys($brokers_info).length == 0}
{#if import.meta.env.NG_DEV}
<div class="text-center max-w-6xl lg:px-8 mx-auto px-4 text-red-800">
<svg
class="animate-bounce mt-10 h-16 w-16 mx-auto"
fill="none"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z"
/>
</svg>
<p class="mb-5">
We could not find a wallet in your browser.<br/>
This is probably due to the recent changes made to the framework.<br/>
Please create a new wallet from this browser and try again,
</p>
</div>
{:else}
<div class="text-center max-w-6xl lg:px-8 mx-auto px-4 text-blue-600">
{#if Object.keys($brokers_info).length == 0} <svg
<!-- <NoWallet /> --> class="y-10 h-16 w-16 mx-auto mb-3"
{#if import.meta.env.NG_DEV} fill="none"
<div class="text-center max-w-6xl lg:px-8 mx-auto px-4 text-red-800"> stroke="currentColor"
<svg stroke-width="1.5"
class="animate-bounce mt-10 h-16 w-16 mx-auto" viewBox="0 0 24 24"
fill="none" xmlns="http://www.w3.org/2000/svg"
stroke="currentColor" aria-hidden="true"
stroke-width="1.5" >
viewBox="0 0 24 24" <path
xmlns="http://www.w3.org/2000/svg" stroke-linecap="round"
aria-hidden="true" stroke-linejoin="round"
> d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z"
<path />
stroke-linecap="round" </svg>
stroke-linejoin="round" We could not find a wallet in your browser.<br/>
d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z" For now, creating a new wallet while a Web App<br/>
/> is authenticating, is not implemented. Please<br/>
</svg> create or import your wallet in a new tab by<br/><a href="https://nextgraph.eu" target="_blank">clicking here</a>
<p class="mb-5"> </div>
We could not find a wallet in your browser.<br/> {/if}
This is probably due to the recent changes made to the framework.<br/>
Please create a new wallet from this browser and try again,
</p>
</div>
{:else} {:else}
<div class="text-center max-w-6xl lg:px-8 mx-auto px-4 text-blue-600"> <Home />
<svg
class="y-10 h-16 w-16 mx-auto mb-3"
fill="none"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z"
/>
</svg>
We could not find a wallet in your browser.<br/>
For now, creating a new wallet while a Web App<br/>
is authenticating, is not implemented. Please<br/>
create or import your wallet in a new tab by<br/><a href="https://nextgraph.eu" target="_blank">clicking here</a>
</div>
{/if} {/if}
{:else if $web_origin}
<Home />
{:else} {:else}
<div class="text-center max-w-6xl lg:px-8 mx-auto px-4 text-red-800"> <div class="text-center max-w-6xl lg:px-8 mx-auto px-4 text-red-800">
<svg <svg

@ -9,45 +9,13 @@
import { import {
writable, writable,
readable,
readonly,
derived,
get,
type Writable, type Writable,
} from "svelte/store"; } from "svelte/store";
import { createAsyncProxy } from "async-proxy";
import { RemoteReadableStream } from 'remote-web-streams';
export const selected_broker = writable<undefined | Object>( undefined ); export const selected_broker = writable<undefined | Object>( undefined );
export const brokers_info = writable( {} ); export const brokers_info = writable( {} );
export const unlocked_wallet = writable(undefined);
export const web_origin = writable(""); export const web_origin = writable("");
import worker_ from "./worker.js?worker&inline"; export const host = writable("");
const worker = new worker_();
async function rpc( method:string, args?: any) : Promise<any> {
const { readable, writablePort } = new RemoteReadableStream();
worker.postMessage({ method, args, port: writablePort }, [writablePort]);
const reader = readable.getReader();
let ret = await reader.read();
await reader.read(); // the close.
return ret.value;
}
const handler = {
async apply(_target: object, path: PropertyKey[], _caller: any, args?: any) :Promise<any> {
if (path[0] === "login") {
} else {
return await rpc(<string>path[0], args);
}
}
};
export const ng = createAsyncProxy({}, handler);

@ -1,26 +0,0 @@
// 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.
//import * as sdk from "@ng-org/wasm-tools-auth";
import { fromWritablePort } from 'remote-web-streams';
self.onmessage = (event) => {
(async function() {
const { method, args, port } = event.data;
const writable = fromWritablePort(port);
const writer = writable.getWriter();
console.log("Message received by worker", method, args);
let ret = await Reflect.apply(sdk[method], null, args);
writer.write(ret);
writer.close();
})();
}
console.log("worker loaded");

@ -126,33 +126,6 @@ async fn main() -> anyhow::Result<()> {
let static_files_bootstrap = warp::get() let static_files_bootstrap = warp::get()
.and(warp::path!("bootstrap" / ..)) .and(warp::path!("bootstrap" / ..))
.and(warp_embed::embed(&BootstrapStatic)) .and(warp_embed::embed(&BootstrapStatic))
.and(warp::query::<HashMap<String, String>>())
.map(|reply, p: HashMap<String, String>| match p.get("o") {
Some(obj) => {
let decoded = obj.trim();
if BSP_DETAILS.get(decoded).is_none()
&& decoded != "http://localhost:14400"
&& decoded != "http://localhost:1421"
{
// rejected (BSP not listed)
warp::http::StatusCode::UNAUTHORIZED.into_response()
} else {
let reply = warp::reply::with_header(
reply,
"Content-Security-Policy",
HeaderValue::from_str(&format!("frame-ancestors 'self' {decoded};"))
.unwrap(),
);
warp::reply::with_header(
reply,
"X-Frame-Options",
HeaderValue::from_str(&format!("ALLOW-FROM {decoded}")).unwrap(),
)
.into_response()
}
}
None => warp::http::StatusCode::BAD_REQUEST.into_response(),
})
.boxed(); .boxed();
let static_files_auth = warp::get() let static_files_auth = warp::get()
@ -162,9 +135,12 @@ async fn main() -> anyhow::Result<()> {
.map(|reply, p: HashMap<String, String>| match p.get("o") { .map(|reply, p: HashMap<String, String>| match p.get("o") {
Some(obj) => { Some(obj) => {
let decoded = obj.trim(); let decoded = obj.trim();
if decoded.eq("*") if BSP_DETAILS.get(decoded).is_none()
|| (!decoded.starts_with("http://") && !decoded.starts_with("https://")) && decoded != "http://localhost:14400"
|| decoded.len() < 11 && decoded != "http://localhost:1421"
// if decoded.eq("*")
// || (!decoded.starts_with("http://") && !decoded.starts_with("https://"))
// || decoded.len() < 11
{ {
warp::http::StatusCode::BAD_REQUEST.into_response() warp::http::StatusCode::BAD_REQUEST.into_response()
} else { } else {
@ -211,8 +187,8 @@ async fn main() -> anyhow::Result<()> {
cors = cors.allow_origin(NG_APP_URL); cors = cors.allow_origin(NG_APP_URL);
cors = cors.allow_origin("http://localhost:14400"); cors = cors.allow_origin("http://localhost:14400");
cors = cors.allow_origin("http://localhost:1421"); cors = cors.allow_origin("http://localhost:1421");
for bsp in BSP_ORIGINS { for bsp in BSP_ORIGINS.iter() {
cors = cors.allow_origin(bsp); cors = cors.allow_origin(*bsp);
} }
log::info!("Starting production server on http://localhost:3033"); log::info!("Starting production server on http://localhost:3033");
warp::serve( warp::serve(

@ -6,6 +6,7 @@
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "vite build --base=./", "build": "vite build --base=./",
"buildfront": "pnpm -C ./web build && pnpm -C ./bootstrap build && pnpm -C ./auth builddev && pnpm -C ./redir builddev",
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {

@ -1,4 +0,0 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg viewBox="0 0 810 540" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" ><desc>European flag</desc>
<defs><g id="s"><g id="c"><path id="t" d="M0,0v1h0.5z" transform="translate(0,-1)rotate(18)"/><use xlink:href="#t" transform="scale(-1,1)"/></g><g id="a"><use xlink:href="#c" transform="rotate(72)"/><use xlink:href="#c" transform="rotate(144)"/></g><use xlink:href="#a" transform="scale(-1,1)"/></g></defs>
<rect fill="#039" width="810" height="540"/><g fill="#fc0" transform="scale(30)translate(13.5,9)"><use xlink:href="#s" y="-6"/><use xlink:href="#s" y="6"/><g id="l"><use xlink:href="#s" x="-6"/><use xlink:href="#s" transform="rotate(150)translate(0,6)rotate(66)"/><use xlink:href="#s" transform="rotate(120)translate(0,6)rotate(24)"/><use xlink:href="#s" transform="rotate(60)translate(0,6)rotate(12)"/><use xlink:href="#s" transform="rotate(30)translate(0,6)rotate(42)"/></g><use xlink:href="#l" transform="scale(-1,1)"/></g>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

@ -11,7 +11,6 @@
<script> <script>
import { Alert } from "flowbite-svelte"; import { Alert } from "flowbite-svelte";
export let params;
</script> </script>
<div class="p-8"> <div class="p-8">

@ -3,8 +3,8 @@
"private": true, "private": true,
"version": "0.1.2", "version": "0.1.2",
"scripts": { "scripts": {
"buildfront": "cargo run-script libwasm && pnpm -C ./app/nextgraph install && pnpm -C ./app/nextgraph webbuild && pnpm -C ./engine/broker/auth install && pnpm -C ./engine/broker/auth build", "buildfront": "cargo run-script libwasm && pnpm -C ./app/nextgraph install && pnpm -C ./app/nextgraph webbuild && pnpm -C ./sdk/js/web build && pnpm -C ./engine/broker/auth install && pnpm -C ./engine/broker/auth build",
"buildfrontdev": "pnpm -C ./engine/broker/auth builddev && pnpm -C ./infra/ngnet/bootstrap builddev && pnpm -C ./infra/ngnet/auth builddev && pnpm -C ./infra/ngnet/redir builddev" "buildfrontdev": "pnpm -C ./engine/broker/auth builddev && pnpm -C ./infra/ngnet/bootstrap builddev && pnpm -C ./infra/ngnet/auth builddev && pnpm -C ./infra/ngnet/redir builddev && pnpm -C ./sdk/js/web builddev"
}, },
"pnpm": { "pnpm": {
"peerDependencyRules": { "peerDependencyRules": {

File diff suppressed because it is too large Load Diff

@ -6,6 +6,7 @@ packages:
- sdk/js/alien-deepsignals - sdk/js/alien-deepsignals
- sdk/js/shex-orm - sdk/js/shex-orm
- sdk/js/examples/multi-framework-signals - sdk/js/examples/multi-framework-signals
- sdk/js/examples/react-ldo
- app/ui-common - app/ui-common
- app/nextgraph - app/nextgraph
- engine/broker/auth - engine/broker/auth

@ -33,7 +33,7 @@ Read our [getting started guide](https://docs.nextgraph.org/en/getting-started/)
// for nodejs // for nodejs
npm i nextgraph npm i nextgraph
// or for browser // or for browser
npm i nextgraphweb npm i @ng-org/web
``` ```
## Publishing to npm ## Publishing to npm

@ -1,5 +1,5 @@
{ {
"name": "multi-framework-signals", "name": "@ng-org/example-multi-framework-signals",
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"type": "module", "type": "module",
@ -15,7 +15,7 @@
}, },
"dependencies": { "dependencies": {
"@ng-org/signals": "workspace:*", "@ng-org/signals": "workspace:*",
"@ng-org/lib-wasm": "workspace:*", "@ng-org/web": "workspace:*",
"@astrojs/react": "4.3.0", "@astrojs/react": "4.3.0",
"vite-plugin-wasm": "^3.5.0", "vite-plugin-wasm": "^3.5.0",
"vite-plugin-top-level-await": "^1.6.0", "vite-plugin-top-level-await": "^1.6.0",

@ -5,12 +5,31 @@ import Highlight from "../components/Highlight.astro";
import VueRoot from "../components/VueRoot.vue"; import VueRoot from "../components/VueRoot.vue";
import ReactRoot from "../components/ReactRoot"; import ReactRoot from "../components/ReactRoot";
import SvelteRoot from "../components/SvelteRoot.svelte"; import SvelteRoot from "../components/SvelteRoot.svelte";
// Hack to get mock backend started // Hack to get mock backend started
//import { mockTestObject } from "../../ng-mock/wasm-land/shapeHandler"; //import { mockTestObject } from "../../ng-mock/wasm-land/shapeHandler";
const title = "Multi-framework app"; const title = "Multi-framework app";
--- ---
<script>
import { ng, init } from "@ng-org/web";
await init(
(event: {
status: string;
session: {
session_id: unknown;
protected_store_id: unknown;
private_store_id: unknown;
public_store_id: unknown;
};
}) => {
console.log(event.status, event.session.session_id);
},
true,
[]
);
</script>
<Layout title={title}> <Layout title={title}>
<Highlight vue> <Highlight vue>
<VueRoot client:only /> <VueRoot client:only />

@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

@ -0,0 +1,57 @@
# example-webapp-react
Example of a Web app made with NextGraph, using React and LDO, and Vite
## NextGraph
> NextGraph brings about the convergence of P2P and Semantic Web technologies, towards a decentralized, secure and privacy-preserving cloud, based on CRDTs.
>
> This open source ecosystem provides solutions for end-users (a platform) and software developers (a framework), wishing to use or create **decentralized** apps featuring: **live collaboration** on rich-text documents, peer to peer communication with **end-to-end encryption**, offline-first, **local-first**, portable and interoperable data, total ownership of data and software, security and privacy. Centered on repositories containing **semantic data** (RDF), **rich text**, and structured data formats like **JSON**, synced between peers belonging to permissioned groups of users, it offers strong eventual consistency, thanks to the use of **CRDTs**. Documents can be linked together, signed, shared securely, queried using the **SPARQL** language and organized into sites and containers.
>
> More info here [https://nextgraph.org](https://nextgraph.org)
## For developing against a public Broker
```
npm install
npm run dev
```
You will have to use a Wallet that was created on one of our public Broker Service Providers (nextgraph.eu by example) before you can actually login. We didn't implement yet the option to create a Wallet while you are using or developing a 3rd party app.
## For developing locally
you need to have a running local ngd server. See those [instructions first](https://git.nextgraph.org/NextGraph/nextgraph-rs/src/branch/master/DEV.md#first-run).
If you are running a local devenv for the frontend of nextGraph on http://localhost:1421 , then (and only then) you need to compile the @ng-org/web package in dev mode:
```
pnpm run -C ../../web builddev
```
Due to the way `npm link` works, you will have to run this command again, after each time you use `npm install`.
Otherwise, if you are using http://localhost:14400 in your browser, just skip the line above, and continue with those:
```
pnpm install
pnpm dev
```
Open this URL in browser : [http://localhost:5173](http://localhost:5173)
See the example code in [src/main.tsx](./src/App.tsx)
## License
Licensed under either of
- Apache License, Version 2.0 ([LICENSE-APACHE2](LICENSE-APACHE2) or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
at your option.
`SPDX-License-Identifier: Apache-2.0 OR MIT`
---
NextGraph received funding through the [NGI Assure Fund](https://nlnet.nl/assure) and the [NGI Zero Commons Fund](https://nlnet.nl/commonsfund/), both funds established by [NLnet](https://nlnet.nl/) Foundation with financial support from the European Commission's [Next Generation Internet](https://ngi.eu/) programme, under the aegis of DG Communications Networks, Content and Technology under grant agreements No 957073 and No 101092990, respectively.

@ -0,0 +1,28 @@
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import tseslint from 'typescript-eslint'
export default tseslint.config(
{ ignores: ['dist'] },
{
extends: [js.configs.recommended, ...tseslint.configs.recommended],
files: ['**/*.{ts,tsx}'],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
plugins: {
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
},
rules: {
...reactHooks.configs.recommended.rules,
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
},
)

@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>NextGraph WebApp React+LDO example</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

@ -0,0 +1,40 @@
{
"name": "@ng-org/example-react-ldo",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc --noEmit && vite build",
"lint": "eslint .",
"preview": "vite preview",
"build:ldo": "ldo build --input src/.shapes --output src/.ldo"
},
"dependencies": {
"@ldo/connected-nextgraph": "^1.0.0-alpha.15",
"@ldo/ldo": "^1.0.0-alpha.15",
"@ldo/react": "^1.0.0-alpha.15",
"@ng-org/web": "workspace:*",
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
"devDependencies": {
"@eslint/js": "^9.22.0",
"@ldo/cli": "^1.0.0-alpha.15",
"@types/jsonld": "^1.5.15",
"@types/react": "^19.0.10",
"@types/react-dom": "^19.0.4",
"@types/shexj": "^2.1.7",
"@vitejs/plugin-react": "^4.3.4",
"autoprefixer": "^10.4.21",
"eslint": "^9.22.0",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.19",
"globals": "^16.0.0",
"postcss": "^8.5.3",
"tailwindcss": "^3.4.17",
"typescript": "~5.7.2",
"typescript-eslint": "^8.26.1",
"vite": "^6.3.1"
}
}

@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

@ -0,0 +1,68 @@
import { LdoJsonldContext } from "@ldo/ldo";
/**
* =============================================================================
* contactContext: JSONLD Context for contact
* =============================================================================
*/
export const contactContext: LdoJsonldContext = {
type: {
"@id": "@type",
},
Individual: {
"@id": "http://www.w3.org/2006/vcard/ns#Individual",
"@context": {
type: {
"@id": "@type",
},
fn: {
"@id": "http://www.w3.org/2006/vcard/ns#fn",
"@type": "http://www.w3.org/2001/XMLSchema#string",
},
hasEmail: {
"@id": "http://www.w3.org/2006/vcard/ns#hasEmail",
"@type": "http://www.w3.org/2001/XMLSchema#string",
},
},
},
Person: {
"@id": "http://schema.org/Person",
"@context": {
type: {
"@id": "@type",
},
fn: {
"@id": "http://www.w3.org/2006/vcard/ns#fn",
"@type": "http://www.w3.org/2001/XMLSchema#string",
},
hasEmail: {
"@id": "http://www.w3.org/2006/vcard/ns#hasEmail",
"@type": "http://www.w3.org/2001/XMLSchema#string",
},
},
},
Person2: {
"@id": "http://xmlns.com/foaf/0.1/Person",
"@context": {
type: {
"@id": "@type",
},
fn: {
"@id": "http://www.w3.org/2006/vcard/ns#fn",
"@type": "http://www.w3.org/2001/XMLSchema#string",
},
hasEmail: {
"@id": "http://www.w3.org/2006/vcard/ns#hasEmail",
"@type": "http://www.w3.org/2001/XMLSchema#string",
},
},
},
fn: {
"@id": "http://www.w3.org/2006/vcard/ns#fn",
"@type": "http://www.w3.org/2001/XMLSchema#string",
},
hasEmail: {
"@id": "http://www.w3.org/2006/vcard/ns#hasEmail",
"@type": "http://www.w3.org/2001/XMLSchema#string",
},
};

@ -0,0 +1,115 @@
import { Schema } from "shexj";
/**
* =============================================================================
* contactSchema: ShexJ Schema for contact
* =============================================================================
*/
export const contactSchema: Schema = {
type: "Schema",
shapes: [
{
id: "did:ng:n:g:x:social:contact#SocialContact",
type: "ShapeDecl",
shapeExpr: {
type: "Shape",
expression: {
type: "EachOf",
expressions: [
{
type: "TripleConstraint",
predicate: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type",
valueExpr: {
type: "NodeConstraint",
values: ["http://www.w3.org/2006/vcard/ns#Individual"],
},
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value: "Defines the node as an Individual (from vcard)",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type",
valueExpr: {
type: "NodeConstraint",
values: ["http://schema.org/Person"],
},
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value: "Defines the node as a Person (from Schema.org)",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type",
valueExpr: {
type: "NodeConstraint",
values: ["http://xmlns.com/foaf/0.1/Person"],
},
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value: "Defines the node as a Person (from foaf)",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/2006/vcard/ns#fn",
valueExpr: {
type: "NodeConstraint",
datatype: "http://www.w3.org/2001/XMLSchema#string",
},
min: 0,
max: 1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value:
"The formatted name of a person. Example: John Smith",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/2006/vcard/ns#hasEmail",
valueExpr: {
type: "NodeConstraint",
datatype: "http://www.w3.org/2001/XMLSchema#string",
},
min: 0,
max: 1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value: "The person's email.",
},
},
],
},
],
},
extra: ["http://www.w3.org/1999/02/22-rdf-syntax-ns#type"],
},
},
],
};

@ -0,0 +1,19 @@
import { ShapeType } from "@ldo/ldo";
import { contactSchema } from "./contact.schema";
import { contactContext } from "./contact.context";
import { SocialContact } from "./contact.typings";
/**
* =============================================================================
* LDO ShapeTypes contact
* =============================================================================
*/
/**
* SocialContact ShapeType
*/
export const SocialContactShapeType: ShapeType<SocialContact> = {
schema: contactSchema,
shape: "did:ng:n:g:x:social:contact#SocialContact",
context: contactContext,
};

@ -0,0 +1,37 @@
import { LdoJsonldContext, LdSet } from "@ldo/ldo";
/**
* =============================================================================
* Typescript Typings for contact
* =============================================================================
*/
/**
* SocialContact Type
*/
export interface SocialContact {
"@id"?: string;
"@context"?: LdoJsonldContext;
/**
* Defines the node as an Individual (from vcard) | Defines the node as a Person (from Schema.org) | Defines the node as a Person (from foaf)
*/
type: LdSet<
| {
"@id": "Individual";
}
| {
"@id": "Person";
}
| {
"@id": "Person2";
}
>;
/**
* The formatted name of a person. Example: John Smith
*/
fn?: string;
/**
* The person's email.
*/
hasEmail?: string;
}

@ -0,0 +1,82 @@
import { LdoJsonldContext } from "@ldo/ldo";
/**
* =============================================================================
* containerContext: JSONLD Context for container
* =============================================================================
*/
export const containerContext: LdoJsonldContext = {
type: {
"@id": "@type",
"@isCollection": true,
},
Container: {
"@id": "http://www.w3.org/ns/ldp#Container",
"@context": {
type: {
"@id": "@type",
"@isCollection": true,
},
modified: {
"@id": "http://purl.org/dc/terms/modified",
"@type": "http://www.w3.org/2001/XMLSchema#string",
},
contains: {
"@id": "http://www.w3.org/ns/ldp#contains",
"@type": "@id",
"@isCollection": true,
},
mtime: {
"@id": "http://www.w3.org/ns/posix/stat#mtime",
"@type": "http://www.w3.org/2001/XMLSchema#decimal",
},
size: {
"@id": "http://www.w3.org/ns/posix/stat#size",
"@type": "http://www.w3.org/2001/XMLSchema#integer",
},
},
},
Resource: {
"@id": "http://www.w3.org/ns/ldp#Resource",
"@context": {
type: {
"@id": "@type",
"@isCollection": true,
},
modified: {
"@id": "http://purl.org/dc/terms/modified",
"@type": "http://www.w3.org/2001/XMLSchema#string",
},
contains: {
"@id": "http://www.w3.org/ns/ldp#contains",
"@type": "@id",
"@isCollection": true,
},
mtime: {
"@id": "http://www.w3.org/ns/posix/stat#mtime",
"@type": "http://www.w3.org/2001/XMLSchema#decimal",
},
size: {
"@id": "http://www.w3.org/ns/posix/stat#size",
"@type": "http://www.w3.org/2001/XMLSchema#integer",
},
},
},
modified: {
"@id": "http://purl.org/dc/terms/modified",
"@type": "http://www.w3.org/2001/XMLSchema#string",
},
contains: {
"@id": "http://www.w3.org/ns/ldp#contains",
"@type": "@id",
"@isCollection": true,
},
mtime: {
"@id": "http://www.w3.org/ns/posix/stat#mtime",
"@type": "http://www.w3.org/2001/XMLSchema#decimal",
},
size: {
"@id": "http://www.w3.org/ns/posix/stat#size",
"@type": "http://www.w3.org/2001/XMLSchema#integer",
},
};

@ -0,0 +1,124 @@
import { Schema } from "shexj";
/**
* =============================================================================
* containerSchema: ShexJ Schema for container
* =============================================================================
*/
export const containerSchema: Schema = {
type: "Schema",
shapes: [
{
id: "http://www.w3.org/ns/lddps#Container",
type: "ShapeDecl",
shapeExpr: {
type: "Shape",
expression: {
id: "http://www.w3.org/ns/lddps#ContainerShape",
type: "EachOf",
expressions: [
{
type: "TripleConstraint",
predicate: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type",
valueExpr: {
type: "NodeConstraint",
values: [
"http://www.w3.org/ns/ldp#Container",
"http://www.w3.org/ns/ldp#Resource",
],
},
min: 0,
max: -1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value: "A container",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://purl.org/dc/terms/modified",
valueExpr: {
type: "NodeConstraint",
datatype: "http://www.w3.org/2001/XMLSchema#string",
},
min: 0,
max: 1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value: "Date modified",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/ns/ldp#contains",
valueExpr: {
type: "NodeConstraint",
nodeKind: "iri",
},
min: 0,
max: -1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value: "Defines a Resource",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/ns/posix/stat#mtime",
valueExpr: {
type: "NodeConstraint",
datatype: "http://www.w3.org/2001/XMLSchema#decimal",
},
min: 0,
max: 1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value: "?",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/ns/posix/stat#size",
valueExpr: {
type: "NodeConstraint",
datatype: "http://www.w3.org/2001/XMLSchema#integer",
},
min: 0,
max: 1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value: "size of this container",
},
},
],
},
],
},
extra: ["http://www.w3.org/1999/02/22-rdf-syntax-ns#type"],
},
},
],
};

@ -0,0 +1,19 @@
import { ShapeType } from "@ldo/ldo";
import { containerSchema } from "./container.schema";
import { containerContext } from "./container.context";
import { Container } from "./container.typings";
/**
* =============================================================================
* LDO ShapeTypes container
* =============================================================================
*/
/**
* Container ShapeType
*/
export const ContainerShapeType: ShapeType<Container> = {
schema: containerSchema,
shape: "http://www.w3.org/ns/lddps#Container",
context: containerContext,
};

@ -0,0 +1,44 @@
import { LdoJsonldContext, LdSet } from "@ldo/ldo";
/**
* =============================================================================
* Typescript Typings for container
* =============================================================================
*/
/**
* Container Type
*/
export interface Container {
"@id"?: string;
"@context"?: LdoJsonldContext;
/**
* A container
*/
type?: LdSet<
| {
"@id": "Container";
}
| {
"@id": "Resource";
}
>;
/**
* Date modified
*/
modified?: string;
/**
* Defines a Resource
*/
contains?: LdSet<{
"@id": string;
}>;
/**
* ?
*/
mtime?: number;
/**
* size of this container
*/
size?: number;
}

@ -0,0 +1,26 @@
# Platform ontologies:
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX owl: <http://www.w3.org/2002/07/owl#>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
PREFIX dc: <http://purl.org/dc/terms/>
# Domain ontology for Contacts in vcard-like form
PREFIX vcard: <http://www.w3.org/2006/vcard/ns#>
PREFIX schem: <http://schema.org/>
PREFIX foaf: <http://xmlns.com/foaf/0.1/>
PREFIX ngx: <did:ng:n:g:x:social:contact#>
ngx:SocialContact EXTRA a {
a [vcard:Individual ]
// rdfs:comment "Defines the node as an Individual (from vcard)" ;
a [ schem:Person ]
// rdfs:comment "Defines the node as a Person (from Schema.org)" ;
a [ foaf:Person ]
// rdfs:comment "Defines the node as a Person (from foaf)" ;
vcard:fn xsd:string ?
// rdfs:comment "The formatted name of a person. Example: John Smith" ;
vcard:hasEmail xsd:string ?
// rdfs:comment "The person's email." ;
}

@ -0,0 +1,24 @@
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX ldp: <http://www.w3.org/ns/ldp#>
PREFIX ldps: <http://www.w3.org/ns/lddps#>
PREFIX dct: <http://purl.org/dc/terms/>
PREFIX stat: <http://www.w3.org/ns/posix/stat#>
PREFIX tur: <http://www.w3.org/ns/iana/media-types/text/turtle#>
PREFIX pim: <http://www.w3.org/ns/pim/space#>
ldps:Container EXTRA a {
$ldps:ContainerShape (
a [ ldp:Container ldp:Resource ]*
// rdfs:comment "A container";
dct:modified xsd:string?
// rdfs:comment "Date modified";
ldp:contains IRI *
// rdfs:comment "Defines a Resource";
stat:mtime xsd:decimal?
// rdfs:comment "?";
stat:size xsd:integer?
// rdfs:comment "size of this container";
)
}

@ -0,0 +1,56 @@
.centered {
/*max-width: 1280px;*/
margin: 0 auto;
padding: 0rem;
text-align: center;
width: fit-content;
}
.contact {
width: 300px;
height: 100px;
background-color: #f6f6f6;
position: relative;
overflow-wrap: anywhere;
}
.name {
width: 300px;
position: absolute;
left: 0;
top: 0;
padding: 5px;
height: 35px;
overflow: hidden;
background-color: #e0e0e0d0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.email-logo {
position: absolute;
left: 5px;
top: 35px;
}
.email {
width: 270px;
position: absolute;
left: 30px;
top: 30px;
padding: 5px;
overflow-wrap: anywhere;
}
input {
margin: 5px;
}
#save {
background-color:rgb(73, 114, 165);
color:white;
cursor:pointer;
}

@ -0,0 +1,22 @@
import React, { FunctionComponent } from 'react';
import { Header } from './Header';
import { Contacts } from './Contacts';
import { BrowserNGLdoProvider } from './reactMethods';
import './App.css'
import "../../../../../app/ui-common/src/styles.css";
const App: FunctionComponent = () => {
return (
<div className="App">
<BrowserNGLdoProvider>
<Header />
<Contacts />
</BrowserNGLdoProvider>
</div>
);
}
export default App

@ -0,0 +1,29 @@
import { FunctionComponent } from "react";
import { useNextGraphAuth } from "./reactMethods";
import { SocialContactShapeType } from "./.ldo/contact.shapeTypes.ts";
import { useSubscribeToResource, useResource, useSubject } from "./reactMethods.ts";
export const Contact: FunctionComponent = ({nuri}) => {
const { session } = useNextGraphAuth();
useResource(session.sessionId && nuri ? nuri : undefined, { subscribe: true });
let contact = useSubject(SocialContactShapeType, session.sessionId && nuri ? nuri.substring(0,53) : undefined);
if (!session.sessionId || !nuri) return <></>;
return <>
{contact.fn? (
<div className="contact" title={nuri}>
<span className="name">
{contact.fn}
</span>
<svg className="w-6 h-6 inline email-logo" data-slot="icon" fill="none" strokeWidth="1.5" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<path strokeLinecap="round" strokeLinejoin="round" d="M16.5 12a4.5 4.5 0 1 1-9 0 4.5 4.5 0 0 1 9 0Zm0 0c0 1.657 1.007 3 2.25 3S21 13.657 21 12a9 9 0 1 0-2.636 6.364M16.5 12V8.25"></path>
</svg>
<span className="email text-left">
email:&nbsp;{contact.hasEmail}
</span>
</div>
) : <></>}
</>;
};

@ -0,0 +1,38 @@
import { FunctionComponent } from "react";
import { useNextGraphAuth } from "./reactMethods";
import { ContainerShapeType } from "./.ldo/container.shapeTypes.ts";
import { useSubscribeToResource, useResource, useSubject } from "./reactMethods.ts";
import { Contact } from "./Contact";
import { MakeContact } from "./MakeContact";
export const Contacts: FunctionComponent = () => {
const { session } = useNextGraphAuth();
let container_overlay: string;
useResource(session.sessionId ? "did:ng:"+session.privateStoreId : undefined, { subscribe: true });
let myContainer = useSubject(ContainerShapeType, session.sessionId ? "did:ng:"+(session.privateStoreId.substring(0,46)) : undefined);
if (session.sessionId) {
container_overlay = session.privateStoreId.substring(46) as string;
}
if (!session.sessionId) return <></>;
return <>
<div className="centered">
<div className="flex flex-wrap justify-center gap-5 mt-10 mb-10">
<MakeContact/>
</div>
<div className="flex flex-wrap justify-center gap-5 mb-10">
{
myContainer.contains?.map(
(contained) =>
<Contact key={contained["@id"]} nuri={contained["@id"]+container_overlay}/>
)
}
</div>
</div>
</>;
};

@ -0,0 +1,44 @@
import { FunctionComponent } from "react";
import { useNextGraphAuth } from "./reactMethods";
export const Header: FunctionComponent = () => {
const { session, login, logout } = useNextGraphAuth();
return (
<div className="full-layout">
{session.sessionId ? (
// If the session is logged in
<div className="p-1 text-white text-center fixed top-0 left-0 right-0" style={{zIndex:1000, height:'36px', backgroundColor:'rgb(73, 114, 165)'}}>
You are logged in.
{/* <span className="font-bold clickable" onClick={logout}> Log out</span> */}
</div>
) : (
// If the session is not logged in
<>
<h1 className="text-2xl text-center mb-10">Welcome to your contact manager</h1>
<div className="text-center text-xl p-1 text-white fixed top-0 left-0 right-0" style={{zIndex:1000, height:'36px', backgroundColor:'rgb(73, 114, 165)'}}>
Please <span className="font-bold clickable" onClick={login}> Log in</span>
</div>
<div className="text-center max-w-6xl lg:px-8 mx-auto px-4 text-blue-800">
<svg className="mt-10 h-16 w-16 mx-auto" data-slot="icon" fill="none" strokeWidth="1.5" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<path strokeLinecap="round" strokeLinejoin="round" d="M8.25 9V5.25A2.25 2.25 0 0 1 10.5 3h6a2.25 2.25 0 0 1 2.25 2.25v13.5A2.25 2.25 0 0 1 16.5 21h-6a2.25 2.25 0 0 1-2.25-2.25V15M12 9l3 3m0 0-3 3m3-3H2.25"></path>
</svg>
<button
onClick={login}
onKeyUp={login}
className="select-none ml-0 mt-2 mb-10 text-white bg-blue-800 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"
>
Please Log in
</button>
</div>
</>
)}
</div>
);
};

@ -0,0 +1,60 @@
import { FormEvent, FunctionComponent, useCallback, useState } from "react";
import { BrowserNGLdoProvider, useLdo, dataset } from './reactMethods';
import { SocialContactShapeType } from "./.ldo/contact.shapeTypes.ts";
import { LdSet } from "@ldo/ldo";
export const MakeContact: FunctionComponent = () => {
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const { createData, commitData } = useLdo();
const onSubmit = useCallback(
async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
const new_name = name.trim();
const new_email = email.trim();
if (new_name.trim().length > 2 && new_email.trim().length > 6 && new_email.indexOf("@") >= 0) {
setName("");
setEmail("");
const resource = await dataset.createResource("nextgraph");
if (!resource.isError) {
//console.log("Created resource:", resource.uri);
const contact = createData(
SocialContactShapeType,
resource.uri.substring(0,53),
resource
);
contact.type = { "@id": "Individual" };
contact.fn = new_name;
contact.hasEmail = new_email;
const result = await commitData(contact);
if (result.isError) {
console.error(result.message);
}
}
}
},
[name, email]
);
return (
<form onSubmit={onSubmit}>
<input
type="text"
placeholder="Enter name"
value={name}
onChange={(e) => setName(e.target.value)}
/>
<input
type="text"
placeholder="Enter email address"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<input type="submit" id="save" value="Save" />
</form>
);
};

@ -0,0 +1,20 @@
import { createContext, useContext } from "react";
/**
* Functions for authenticating with NextGraph
*/
export interface NGWalletAuthFunctions {
login: () => Promise<void>;
logout: () => Promise<void>;
session: unknown;
ranInitialAuthCheck: boolean;
}
// There is no initial value for this context. It will be given in the provider
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
export const NextGraphAuthContext = createContext<NGWalletAuthFunctions>(undefined);
export function useNextGraphAuth(): NGWalletAuthFunctions {
return useContext(NextGraphAuthContext);
}

@ -0,0 +1,112 @@
import React, { useCallback, useEffect, useMemo, useState } from "react";
import type { FunctionComponent, PropsWithChildren } from "react";
import { NextGraphAuthContext, useNextGraphAuth } from "./NextGraphAuthContext";
import {ng, init} from "@ng-org/web";
import type { ConnectedLdoDataset, ConnectedPlugin } from "@ldo/connected";
import type { NextGraphConnectedPlugin, NextGraphConnectedContext } from "@ldo/connected-nextgraph";
/**
* Creates special react methods specific to the NextGraph Auth
* @param dataset the connectedLdoDataset with a nextGraphConnectedPlugin
* @returns { BrowserNGLdoProvider, useNextGraphAuth }
*/
export function createBrowserNGReactMethods(
dataset: ConnectedLdoDataset<(NextGraphConnectedPlugin | ConnectedPlugin)[]>,
) {
const BrowserNGLdoProvider: FunctionComponent<PropsWithChildren> = ({
children,
}) => {
const [session, setSession] = useState<NextGraphConnectedContext>(
{
ng: undefined,
}
);
const [ranInitialAuthCheck, setRanInitialAuthCheck] = useState(false);
const runInitialAuthCheck = useCallback(async () => {
//console.log("runInitialAuthCheck called", ranInitialAuthCheck)
if (ranInitialAuthCheck) return;
//console.log("init called");
setRanInitialAuthCheck(true);
// TODO: export the types for the session object coming from NG.
await init( (event: { status: string; session: { session_id: unknown; protected_store_id: unknown; private_store_id: unknown; public_store_id: unknown; }; }) => {
//console.log("called back in react", event)
// callback
// once you receive event.status == "loggedin"
// you can use the full API
if (event.status == "loggedin") {
setSession({
ng,
sessionId: event.session.session_id as string, //FIXME: sessionId should be a Number.
protectedStoreId: event.session.protected_store_id as string,
privateStoreId: event.session.private_store_id as string,
publicStoreId: event.session.public_store_id as string
}); // TODO: add event.session.user too
dataset.setContext("nextgraph", {
ng,
sessionId: event.session.session_id as string
});
}
else if (event.status == "cancelled" || event.status == "error" || event.status == "loggedout") {
setSession({ ng: undefined });
dataset.setContext("nextgraph", {
ng: undefined,
});
}
}
, true // singleton: boolean (should it be launched as a unique instance, or will your app create many docs in the system)
, []); //list of AccessRequests (for now, leave this empty)
}, []);
const login = useCallback(
async () => {
await ng.login();
},
[],
);
const logout = useCallback(async () => {
await ng.logout();
}, []);
useEffect(() => {
runInitialAuthCheck();
}, []);
const nextGraphAuthFunctions = useMemo(
() => ({
runInitialAuthCheck,
login,
logout,
session,
ranInitialAuthCheck,
}),
[
login,
logout,
ranInitialAuthCheck,
runInitialAuthCheck,
session,
],
);
return (
<NextGraphAuthContext.Provider value={nextGraphAuthFunctions}>
{children}
</NextGraphAuthContext.Provider>
);
};
return {
BrowserNGLdoProvider,
useNextGraphAuth: useNextGraphAuth
};
};

@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

@ -0,0 +1,10 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.tsx'
createRoot(document.getElementById('app')!).render(
// <StrictMode>
<App />
// </StrictMode>,
)

@ -0,0 +1,18 @@
import { nextGraphConnectedPlugin } from "@ldo/connected-nextgraph";
import { createLdoReactMethods } from "@ldo/react";
import { createBrowserNGReactMethods } from "./createBrowserNGReactMethods";
export const {
dataset,
useLdo,
useMatchObject,
useMatchSubject,
useResource,
useSubject,
useSubscribeToResource,
} = createLdoReactMethods([nextGraphConnectedPlugin]);
const methods = createBrowserNGReactMethods(dataset);
export const { BrowserNGLdoProvider, useNextGraphAuth } = methods;

@ -0,0 +1 @@
/// <reference types="vite/client" />

@ -0,0 +1,12 @@
/** @type {import('tailwindcss').Config} */
export default {
content: [
"./index.html",
"./src/**/*.{js,jsx,ts,tsx}"
],
theme: {
extend: {},
},
plugins: [],
}

@ -0,0 +1,26 @@
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["src"]
}

@ -0,0 +1,7 @@
{
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }
]
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save