feat/orm-diffs
Niko PLP 2 days ago
parent 2f9a2d8668
commit 9c1165653b
  1. 30
      engine/broker/auth/index.html
  2. 20
      engine/broker/src/server_ws.rs
  3. 2
      engine/net/src/bsps.rs
  4. 24
      infra/ngnet/auth/.gitignore
  5. 88
      infra/ngnet/auth/index.html
  6. 32
      infra/ngnet/auth/jsconfig.json
  7. 40
      infra/ngnet/auth/package.json
  8. 1930
      infra/ngnet/auth/pnpm-lock.yaml
  9. 13
      infra/ngnet/auth/postcss.config.cjs
  10. 32
      infra/ngnet/auth/prepare-app-file.cjs
  11. 180
      infra/ngnet/auth/src/App.svelte
  12. 4
      infra/ngnet/auth/src/app.postcss
  13. 4
      infra/ngnet/auth/src/assets/EU.svg
  14. 16
      infra/ngnet/auth/src/assets/nextgraph.svg
  15. 160
      infra/ngnet/auth/src/lib/Home.svelte
  16. 120
      infra/ngnet/auth/src/main.ts
  17. 99
      infra/ngnet/auth/src/routes/Home.svelte
  18. 53
      infra/ngnet/auth/src/store.ts
  19. 2
      infra/ngnet/auth/src/vite-env.d.ts
  20. 26
      infra/ngnet/auth/src/worker.js
  21. 13
      infra/ngnet/auth/svelte.config.js
  22. 38
      infra/ngnet/auth/tailwind.config.cjs
  23. 70
      infra/ngnet/auth/vite.config.js
  24. 2
      infra/ngnet/bootstrap/package.json
  25. 6
      infra/ngnet/redir/package.json
  26. 2
      infra/ngnet/redir/src/App.svelte
  27. 2
      infra/ngnet/redir/src/main.ts
  28. 22
      infra/ngnet/src/main.rs
  29. 2
      infra/ngnet/web/src/main.js
  30. 645
      pnpm-lock.yaml
  31. 3
      pnpm-workspace.yaml
  32. 24
      sdk/js/web/.gitignore
  33. 16
      sdk/js/web/LICENSE-APACHE2
  34. 22
      sdk/js/web/LICENSE-MIT
  35. 165
      sdk/js/web/README.md
  36. 75
      sdk/js/web/package.json
  37. 194
      sdk/js/web/src/index.ts
  38. 1
      sdk/js/web/src/vite-env.d.ts
  39. 24
      sdk/js/web/tsconfig.json
  40. 16
      sdk/js/web/vite.config.ts

@ -25,10 +25,40 @@
.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>
</head>
<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 style="flex-direction: column;justify-content: center;color:#4972a5;width:100%;text-align:center;font-family: Inter, Avenir, Helvetica, Arial, sans-serif;">
<svg

@ -301,11 +301,11 @@ fn upgrade_ws_or_serve_app(
.status(StatusCode::NOT_MODIFIED)
.header("Cache-Control", "max-age=31536000, must-revalidate")
.header("ETag", sha)
.header(
"Content-Security-Policy",
format!("frame-ancestors 'self' https://nextgraph.net {webapp_origin};"),
)
.header("X-Frame-Options", format!("ALLOW-FROM {webapp_origin}"))
// .header(
// "Content-Security-Policy",
// format!("frame-ancestors 'self' https://nextgraph.net {webapp_origin};"),
// )
// .header("X-Frame-Options", format!("ALLOW-FROM {webapp_origin}"))
.body(None)
.unwrap();
return Err(res);
@ -313,11 +313,11 @@ fn upgrade_ws_or_serve_app(
let file = AppAuth::get("index.gzip").unwrap();
let res = Response::builder()
.status(StatusCode::OK)
.header(
"Content-Security-Policy",
format!("frame-ancestors 'self' https://nextgraph.net {webapp_origin};"),
)
.header("X-Frame-Options", format!("ALLOW-FROM {webapp_origin}"))
// .header(
// "Content-Security-Policy",
// format!("frame-ancestors 'self' https://nextgraph.net {webapp_origin};"),
// )
// .header("X-Frame-Options", format!("ALLOW-FROM {webapp_origin}"))
.header("Content-Type", "text/html")
.header("Cache-Control", "max-age=31536000, must-revalidate")
.header("Content-Encoding", "gzip")

@ -51,5 +51,5 @@ lazy_static! {
d
};
pub static ref BSP_ORIGINS: Vec<&'static str> = { BSP_DETAILS.keys().cloned().collect() };
pub static ref BSP_ORIGINS: Vec<&'static str> = BSP_DETAILS.keys().cloned().collect();
}

@ -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,88 @@
<!--
// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>NextGraph Auth</title>
<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>
</head>
<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 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>
</body>
</html>

@ -0,0 +1,32 @@
{
"compilerOptions": {
"moduleResolution": "bundler",
"target": "ESNext",
"module": "ESNext",
/**
* svelte-preprocess cannot figure out whether you have
* a value or a type, so tell TypeScript to enforce using
* `import type` instead of `import` for Types.
*/
"verbatimModuleSyntax": true,
"isolatedModules": true,
"resolveJsonModule": true,
/**
* To have warnings / errors of the Svelte compiler at the
* correct position, enable source maps by default.
*/
"sourceMap": true,
"esModuleInterop": true,
"skipLibCheck": true,
/**
* Typecheck JS in `.svelte` and `.js` files by default.
* Disable this if you'd like to use dynamic types.
*/
"checkJs": true
},
/**
* Use global.d.ts instead of compilerOptions.types
* to avoid limiting type declarations.
*/
"include": ["src/**/*.d.ts", "src/**/*.ts", "src/**/*.js", "src/**/*.svelte"]
}

@ -0,0 +1,40 @@
{
"name": "@ng-org/ngnet-auth",
"private": true,
"version": "0.1.2",
"type": "module",
"scripts": {
"dev": "vite",
"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",
"preview": "vite preview"
},
"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": {
"shx": "^0.3.4",
"cross-env": "^7.0.3",
"node-gzip": "^1.1.2",
"@sveltejs/vite-plugin-svelte": "^2.0.4",
"svelte": "^3.58.0",
"vite": "^4.3.9",
"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",
"autoprefixer": "^10.4.14",
"vite-plugin-svelte-svg": "^2.2.1",
"vite-plugin-top-level-await": "1.3.1",
"vite-plugin-singlefile": "0.13.5",
"vite-plugin-wasm": "3.2.2"
}
}

File diff suppressed because it is too large Load Diff

@ -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,32 @@
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,()=>{});
})

@ -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 } 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}

@ -0,0 +1,4 @@
/* Write your global styles here, in PostCSS syntax */
@tailwind base;
@tailwind components;
@tailwind utilities;

@ -0,0 +1,4 @@
<?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>

After

Width:  |  Height:  |  Size: 1.1 KiB

@ -0,0 +1,16 @@
<?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>

After

Width:  |  Height:  |  Size: 2.9 KiB

@ -0,0 +1,160 @@
<!--
// 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}

@ -0,0 +1,120 @@
// 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 "./app.postcss";
import "../../../../app/ui-common/src/styles.css";
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";
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({
target: document.getElementById("app"),
});
export default app;

@ -0,0 +1,99 @@
<!--
// 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}

@ -0,0 +1,53 @@
// 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);

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

@ -0,0 +1,26 @@
// 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");

@ -0,0 +1,13 @@
import preprocess from "svelte-preprocess";
import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";
const config = {
// preprocess: [
// vitePreprocess(),
// preprocess({
// postcss: true,
// }),
// ],
};
export default config;

@ -0,0 +1,38 @@
/** @type {import('tailwindcss').Config}*/
const defaultTheme = require('tailwindcss/defaultTheme')
const config = {
content: [
"./src/**/*.{html,js,svelte,ts}",
"./node_modules/flowbite-svelte/**/*.{html,js,svelte,ts}",
"./node_modules/@nng-org/ui-common/src/**/*.{html,js,svelte,ts}",
],
theme: {
extend: {
colors: {
primary: { "50": "#eff6ff", "100": "#dbeafe", "200": "#bfdbfe", "300": "#93c5fd", "400": "#60a5fa", "500": "#3b82f6", "600": "#1E88E5", "700": "#4972A5", "800": "#1e40af", "900": "#1e3a8a" }
},
},
screens: {
'xxs': '400px',
'xs': '500px',
...defaultTheme.screens,
'tall': { 'raw': '(min-height: 450px)' },
'tall-xxs': { 'raw': '(min-height: 360px)' },
'tall-xs': { 'raw': '(min-height: 480px)' },
'tall-sm': { 'raw': '(min-height: 640px)' },
'tall-md': { 'raw': '(min-height: 800px)' },
'tall-l': { 'raw': '(min-height: 1000px)' },
'tall-xl': { 'raw': '(min-height: 1200px)' },
'tall-xxl': { 'raw': '(min-height: 1400px)' },
},
},
plugins: [
require('flowbite/plugin'),
require('@tailwindcss/typography')
],
darkMode: 'selector',
};
module.exports = config;

@ -0,0 +1,70 @@
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"
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/
export default defineConfig({
envPrefix: ["VITE_", "NG_"],
server: {
port: 14402
},
worker: {
format: 'es',
plugins : [
topLevelAwait(),
wasm(),
viteSingleFile()
]
},
plugins: [
topLevelAwait(),
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(),
]
})

@ -1,5 +1,5 @@
{
"name": "@ng-org/net-bootstrap",
"name": "@ng-org/ngnet-bootstrap",
"private": true,
"version": "0.1.2",
"type": "module",

@ -1,12 +1,12 @@
{
"name": "@ng-org/net-redir",
"name": "@ng-org/ngnet-redir",
"private": true,
"version": "0.1.2",
"type": "module",
"scripts": {
"dev": "vite",
"build": "cd ../wasm-tools && cargo run-script app && cd ../net-auth && vite build --base=./ && shx rm -rf ./dist/assets",
"builddev": "cd ../wasm-tools && cargo run-script app && cd ../net-auth && 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",
"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",
"preview": "vite preview"
},
"dependencies": {

@ -31,7 +31,7 @@
WalletLoginQr,
WalletLoginTextCode
} from "@ng-org/ui-common/routes";
import { Bowser } from "../../../ng-sdk-js/js/bowser.js";
import { Bowser } from "../../../../sdk/js/lib-wasm/jsland/bowser.js";
import Home from "./routes/Home.svelte";

@ -8,7 +8,7 @@
// according to those terms.
import "./app.postcss";
import "../../../common/src/styles.css";
import "../../../../app/ui-common/src/styles.css";
import { link, push } from "svelte-spa-router";
import App from "./App.svelte";
import { fromWritablePort, RemoteReadableStream } from 'remote-web-streams';

@ -31,7 +31,7 @@ use ng_repo::utils::timestamp_after;
use ng_net::actors::admin::add_invitation::*;
use ng_net::broker::BROKER;
use ng_net::bsps::BSP_DETAILS;
use ng_net::bsps::{BSP_DETAILS, BSP_ORIGINS};
use ng_net::types::{
AdminResponseContentV0, BindAddress, CreateAccountBSP, Invitation, InvitationCode,
APP_ACCOUNT_REGISTERED_SUFFIX, NG_APP_URL, NG_NET_URL,
@ -44,11 +44,15 @@ use ng_client_ws::remote_ws::ConnectionWebSocket;
struct Static;
#[derive(RustEmbed)]
#[folder = "../net-auth/dist"]
#[folder = "auth/dist"]
struct AuthStatic;
#[derive(RustEmbed)]
#[folder = "../net-bootstrap/dist"]
#[folder = "redir/dist"]
struct RedirStatic;
#[derive(RustEmbed)]
#[folder = "bootstrap/dist"]
struct BootstrapStatic;
struct Server {}
@ -111,6 +115,11 @@ async fn main() -> anyhow::Result<()> {
//.with(warp::reply::with::headers(headers))
.boxed();
let static_files_redir = warp::get()
.and(warp_embed::embed(&RedirStatic))
//.with(warp::reply::with::headers(headers))
.boxed();
// just doing that to lazy load it.
BSP_DETAILS.len();
@ -202,11 +211,13 @@ async fn main() -> anyhow::Result<()> {
cors = cors.allow_origin(NG_APP_URL);
cors = cors.allow_origin("http://localhost:14400");
cors = cors.allow_origin("http://localhost:1421");
// TODO when there will be an API again, we will call it from any BSPs.
// we should add the list of all BSPs origin's here
for bsp in BSP_ORIGINS {
cors = cors.allow_origin(bsp);
}
log::info!("Starting production server on http://localhost:3033");
warp::serve(
static_files
.or(static_files_redir)
.or(static_files_auth.or(static_files_bootstrap))
.with(cors)
.with(incoming_log),
@ -221,6 +232,7 @@ async fn main() -> anyhow::Result<()> {
log::info!("Starting server on http://localhost:3033");
warp::serve(
static_files
.or(static_files_redir)
.or(static_files_auth.or(static_files_bootstrap))
.with(cors)
.with(incoming_log),

@ -1,5 +1,5 @@
import './app.postcss'
import "../../../../common/src/styles.css";
import "../../../../app/ui-common/src/styles.css";
import App from './App.svelte'
const app = new App({

File diff suppressed because it is too large Load Diff

@ -1,6 +1,7 @@
packages:
- sdk/js/lib-wasm/pkg
- sdk/js/api-web
- sdk/js/web
- sdk/js/signals
- sdk/js/alien-deepsignals
- sdk/js/shex-orm
@ -12,6 +13,8 @@ packages:
- infra/ngapp/web
- infra/ngnet/bootstrap
- infra/ngnet/redir
- infra/ngnet/web
- infra/ngnet/auth
onlyBuiltDependencies:
- "@parcel/watcher"
- "@swc/core"

@ -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,16 @@
Apache 2.0 License
Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

@ -0,0 +1,22 @@
MIT License
Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

@ -0,0 +1,165 @@
# @ng-org/web
[![Apache 2.0 Licensed][license-image]][license-link]
[![MIT Licensed][license-image2]][license-link2]
[![project chat](https://img.shields.io/badge/zulip-join_chat-brightgreen.svg)](https://forum.nextgraph.org)
JavaScript/TypeScript package containing the SDK of NextGraph for developing third-party Web Apps
## 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)
## Support
Documentation can be found here [https://docs.nextgraph.org](https://docs.nextgraph.org)
And our community forum where you can ask questions is here [https://forum.nextgraph.org](https://forum.nextgraph.org)
## For developers
Read our [getting started guide](https://docs.nextgraph.org/en/getting-started/).
You need to create a Wallet for yourself, on one of our Public Broker Service Provider. Alternatively, you can do everything locally, as [described below](#local-development)
```
npm i nextgraphweb
```
Additionally, you can use [LDO (Linked Data Object) library](https://ldo.js.org/latest/guides/nextgraph/) to help you with RDF handling in the client side.
```
npm i @ldo/connected-nextgraph
```
More documentation on LDO can be found [here](https://www.npmjs.com/package/@ldo/connected-nextgraph).
The LDO library also offers a React plugin that will be demonstrated in another example.
You will find a full example web app [here](https://git.nextgraph.org/NextGraph/nextgraph-rs/src/branch/master/ng-sdk-js/example-webapp-vite).
Specially you will find there instructions for setting up your local dev env.
You have to first call the `init()` and `ng.login()`, then once you receive the status `loggedin` in the callback, you can start using the whole API.
## APIs
All the functions are async. you must use them with `await` (or `.then()`).
They all can throw errors. You must enclose them in `try {} catch(e) {}`
- `ng.doc_create()`
- `ng.sparql_query(session_id, "[SPARQL query]", base, nuri)` returns or:
- for SELECT queries: a JSON Sparql Query Result as a Javascript object. [SPARQL Query Results JSON Format](https://www.w3.org/TR/sparql11-results-json/)
- for CONSTRUCT queries: a list of quads in the format [RDF-JS data model](http://rdf.js.org/data-model-spec/) that can be used as ingress to RDFjs lib.
- for ASK queries: a boolean
- `ng.sparql_update(session_id, "[SPARQL update]")` returns nothing, but can throw an error.
Here is the format of the config object to be supplied in the calls to `init_headless` and `admin_create_user`:
## Local development
you need to have a running local ngd server and a local ng-app frontend too. See those [instructions first](https://git.nextgraph.org/NextGraph/nextgraph-rs/src/branch/master/DEV.md#first-run).
You will need to create an admin wallet on the local ngd instance, as explained in the above link.
Then you can use that wallet to log in inside your webapp.
Then compile the nextgraphweb package in dev mode:
```
pnpm run -C ../../helpers/nextgraphweb builddev
```
Then create your app, by example, with the command:
```
npm create vite@latest
```
change directory to where our app is located. and install dependencies, then run the dev server.
```
npm install
npm link ../../helpers/nextgraphweb
npm run dev
```
Due to the way `npm link` works, you will have to run this command again, after every time you use `npm install`.
See the example code in [src/main.js](./src/main.js)
## Example usage
call :
```javascript
import { default as ng, init } from "nextgraphweb";
await init(
(event) => {
// callback
// once you receive event.status == "loggedin"
// you can use the full API
},
true, // singleton: boolean (will your app create many docs in the system, or should it be launched as a unique instance)
[]
); //list of AccessRequests (for now, leave this empty)
await ng.login(); // this will return false at the first attempt. but it will open the wallet login page so the user can login.
// if you call it again later once the user has logged in already, it will return true, and nothing more will happen
```
You can alternatively wrap the callback inside a Promise in order to wait for the "loggedin" event.
```javascript
import {default as ng, init} from "nextgraphweb";
let loggedin = new Promise( async (resolve) => {
await init( (event) => {
// callback
// once you receive event.status == "loggedin"
// you can use the full API
if (event.status == "loggedin") resolve(event.session);
else if (event.status == "cancelled" || event.status == "error") resolve(false);
}
, true // singleton: boolean (will your app create many docs in the system, or should it be launched as a unique instance)
, []); //list of AccessRequests (for now, leave this empty)
});
await ng.login(); // this will return false at the first attempt. but it will open the wallet login page so the user can login.
// if you call it again later once the user has logged in already, it will return true, and nothing more will happen
let session = await loggedin;
if (session) {
await ng.doc_create(session.session_id,...);
await ng.sparql_query(session.session_id,...);
}
```
---
## 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.
[license-image]: https://img.shields.io/badge/license-Apache2.0-blue.svg
[license-link]: https://git.nextgraph.org/NextGraph/nextgraph-rs/raw/branch/master/LICENSE-APACHE2
[license-image2]: https://img.shields.io/badge/license-MIT-blue.svg
[license-link2]: https://git.nextgraph.org/NextGraph/nextgraph-rs/src/branch/master/LICENSE-MIT

@ -0,0 +1,75 @@
{
"name": "@ng-org/web",
"collaborators": [
"Niko PLP <niko@nextgraph.org>"
],
"description": "JS/TS SDK of NextGraph for third-party web apps",
"version": "0.1.2",
"license": "MIT/Apache-2.0",
"repository": {
"type": "git",
"url": "https://git.nextgraph.org/NextGraph/nextgraph-rs"
},
"type": "module",
"files": ["dist"],
"main": "./dist/ngweb.umd.cjs",
"module": "./dist/ngweb.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import":{
"types": "./dist/index.d.ts",
"default": "./dist/ngweb.js"
},
"require": {
"types": "./dist/index.d.ts",
"default": "./dist/ngweb.umd.cjs"
}
}
},
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"builddev": "tsc && cross-env NG_DEV=1 vite build",
"preview": "vite preview"
},
"dependencies": {
"async-proxy": "^0.4.1",
"remote-web-streams": "^0.2.0"
},
"devDependencies": {
"@types/node": "^18.7.10",
"typescript": "~5.7.2",
"vite": "^6.2.0",
"vite-plugin-dts": "^4.5.3",
"cross-env": "^7.0.3"
},
"keywords": [
"crdt",
"dapp",
"decentralized",
"e2ee",
"local-first",
"p2p",
"semantic-web",
"eventual-consistency",
"json-ld",
"markdown",
"ocap",
"vc",
"offline-first",
"p2p-network",
"collaboration",
"privacy-protection",
"rdf",
"rich-text-editor",
"self-hosted",
"sparql",
"byzantine-fault-tolerance",
"web3",
"graph-database",
"database",
"triplestore"
],
"homepage": "https://nextgraph.org"
}

@ -0,0 +1,194 @@
// 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 { createAsyncProxy } from "async-proxy";
import { RemoteReadableStream } from 'remote-web-streams';
let initialized: null | Window = null;
const css = `.nextgraph-auth-modal {
visibility: hidden;
background-color: rgba(0, 0, 0, 0.4);
display: grid;
place-items: center;
height: 100vh;
width: 100vw;
position: fixed;
left: 0;
top: 0;
}
.nextgraph-auth-modal.nextgraph-auth-modal--fade {
opacity: 0;
transition: 0.2s;
}
.nextgraph-auth-modal.nextgraph-auth-modal--active {
visibility: visible;
z-index: 9999;
opacity: 1;
}
.nextgraph-auth-modal__content {
width: 100%;
height: 100%;
background-color: #fff;
}
.nextgraph-auth-modal.nextgraph-auth-modal--active.nextgraph-auth-modal--fade {
opacity: 1;
animation-name: fadeInOpacity;
animation-iteration-count: 1;
animation-timing-function: ease-in;
animation-duration: 0.5s;
}
@keyframes fadeInOpacity {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}`;
const html = `
<div id="nextgraph-auth" class="nextgraph-auth-modal nextgraph-auth-modal--fade">
<div class="nextgraph-auth-modal__content">
<iframe id="nextgraph-auth-iframe" scrolling="auto" frameborder="0"
style="position: relative; height: 100%; width: 100%; overflow:auto;">
</iframe>
</div>
</div>`;
// const javascript = `
// `;
function addTags() {
const style = window.document.createElement('style');
style.textContent = css;
window.document.head.append(style);
let body = document.getElementsByTagName("body")[0];
body.insertAdjacentHTML("afterbegin", html);
// const js = window.document.createElement('script');
// js.type = "text/javascript";
// js.textContent = javascript;
// body.append(js);
}
const iframe_config = 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"} ;
// when developing net-auth
//const iframe_config = {src:"http://localhost:14402/?o=", origin: "http://localhost:14402"};
// to test ngnet
//const iframe_config = {src:"http://127.0.0.1:3033/auth/?o=", origin: "http://127.0.0.1:3033"};
export const init = async function(callback:Function, singleton:boolean, access_requests:any) {
if (initialized === null) {
if (!window) throw new Error("init(callback,..) can only be called from a browser's window");
let origin = location.origin;
let encoded_origin = encodeURIComponent(origin);
addTags();
let iframe: HTMLIFrameElement = <HTMLIFrameElement>window.document.getElementById('nextgraph-auth-iframe');
if (iframe) {
return new Promise(async (resolve) => {
iframe.addEventListener("load", async function() {
initialized = this.contentWindow;
const { readable, writablePort } = new RemoteReadableStream();
initialized?.postMessage({ method: "init", singleton, access_requests, port: writablePort }, iframe_config.origin, [writablePort]);
const reader = readable.getReader();
resolve(true);
for (var msg; msg = await reader.read(); ) {
if (msg.done) break;
if (msg.value.status == "error") {
console.error(msg.value.error);
} else if ( msg.value.status == "cancelled") {
hide_nextgraph_auth();
} else if (msg.value.status == "loggedin") {
hide_nextgraph_auth();
}
await (callback)(msg.value);
}
});
iframe.src = `${iframe_config.src}${encoded_origin}`;
});
}
}
}
function show_nextgraph_auth() {
window.document.getElementById("nextgraph-auth")?.classList.add('nextgraph-auth-modal--active');
}
function hide_nextgraph_auth() {
window.document.getElementById("nextgraph-auth")?.classList.remove('nextgraph-auth-modal--active');
}
async function rpc( method:string, args?: any) : Promise<any> {
const { readable, writablePort } = new RemoteReadableStream();
//console.log("POSTING",method, args);
if (method==="doc_subscribe") {
let callback = args[2];
let new_args = [args[0],args[1]];
initialized?.postMessage({ method, args:new_args, port: writablePort }, iframe_config.origin, [writablePort]);
const reader = readable.getReader();
let unsub = new Promise(async (resolve)=> {
resolve(()=>{
// unsub function that does nothing.
//TODO: implement it
});
for (var msg; msg = await reader.read(); ) {
if (msg.done) break;
if (msg.value.error) {
throw new Error(msg.value.ret);
} else if (msg.value.stream) {
(callback)(msg.value.ret);
}
// TODO: deal with end of stream
}
});
return unsub;
} else {
initialized?.postMessage({ method, args, port: writablePort }, iframe_config.origin, [writablePort]);
const reader = readable.getReader();
let ret = await reader.read();
//console.log(ret)
await reader.read(); // the close
if (ret.value.ok)
return ret.value.ret;
else
throw new Error(ret.value.ret);
}
}
const handler = {
async apply(_target: object, path: PropertyKey[], _caller: any, args?: any) :Promise<any> {
if (initialized === null) {
throw new Error("you must call init() first (and once)");
}
if (path[0] === "login") {
if (await rpc("login") !== true) {
show_nextgraph_auth();
return false;
} else {
return true;
}
} else {
return await rpc(<string>path[0], args);
}
}
};
export const ng = createAsyncProxy({}, handler);

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

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

@ -0,0 +1,16 @@
import { resolve } from 'path';
import { defineConfig } from 'vite';
import dts from 'vite-plugin-dts';
// https://vitejs.dev/guide/build.html#library-mode
export default defineConfig({
build: {
lib: {
entry: resolve(__dirname, 'src/index.ts'),
name: 'ngweb',
fileName: 'ngweb',
},
},
envPrefix: ["VITE_", "NG_"],
plugins: [dts()],
});
Loading…
Cancel
Save