Rust implementation of NextGraph, a Decentralized and local-first web 3.0 ecosystem https://nextgraph.org
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
nextgraph-rs/infra/ngnet/bootstrap/src/App.svelte

180 lines
5.7 KiB

<!--
// 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} />