Skip to content
Draft
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions assets/locales/en.po
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ msgstr "Unbounded"
msgid "help_fight_global_internet_censorship"
msgstr "Help Fight Global Internet Censorship"

msgid "share_bandwidth"
msgstr "Share Bandwidth"

msgid "unbounded_description"
msgstr "When enabled, Unbounded shares a small amount of your bandwidth to help people in censored countries access the open internet."

msgid "vpn_settings"
msgstr "VPN Settings"

Expand Down
256 changes: 256 additions & 0 deletions assets/unbounded/globe.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>Unbounded Globe</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body {
width: 100%; height: 100%;
overflow: hidden;
background: transparent;
}
#globe-container {
width: 100%; height: 100%;
display: flex;
align-items: center;
justify-content: center;
position: relative;
}
#globe-container canvas { touch-action: none; }
</style>
</head>
<body>
<div id="globe-container"></div>

<script src="https://unpkg.com/three@0.160.0/build/three.min.js"></script>
<script src="https://unpkg.com/three-globe@2.27.4/dist/three-globe.min.js"></script>
<script src="https://unpkg.com/three@0.160.0/examples/js/controls/OrbitControls.js"></script>
<script>
(function() {
'use strict';

// --- Configuration ---
const COLORS = {
arcOrigin: 'rgba(0, 188, 212, 0.75)',
arcPeer: 'rgba(255, 193, 7, 0.75)',
pointOrigin: 'rgba(0, 188, 212, 0.15)',
pointPeer: 'rgba(255, 193, 7, 0.15)',
atmosphere: 'rgba(0, 188, 212, 1)',
gapFill: 'rgba(0, 188, 212, 0.4)',
};
const ARCH_ALTITUDE_MIN = 0.3;
const ARCH_ALTITUDE_GAP = 0.05;
const UV_MAP_DARK = 'https://embed.lantern.io/uv-map-dark.png';

// --- State ---
let arcs = [];
let points = [];
let originLat = null;
let originLng = null;
let countryPeerCounts = {};

// --- Globe Setup ---
const container = document.getElementById('globe-container');
const width = () => container.clientWidth;
const height = () => container.clientHeight;

const globe = new ThreeGlobe()
.globeImageUrl(UV_MAP_DARK)
.showAtmosphere(true)
.atmosphereColor(COLORS.atmosphere)
.atmosphereAltitude(0.25)
.arcColor('color')
.arcDashLength(1)
.arcDashGap(0.5)
.arcDashInitialGap(1)
.arcDashAnimateTime(1000)
.arcStroke(d => d.ghost ? 10 : 2)
.arcsTransitionDuration(0)
.pointColor('color')
.pointRadius(4)
.pointAltitude(0)
.pointsTransitionDuration(500);

// Three.js scene
const renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true,
powerPreference: 'high-performance',
});
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.setSize(width(), height());
renderer.setClearColor(0x000000, 0);
container.appendChild(renderer.domElement);

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(50, width() / height(), 1, 5000);
camera.position.z = 400;

// Lighting
const ambient = new THREE.AmbientLight(0xffffff, 0.6);
scene.add(ambient);
const dirLight = new THREE.DirectionalLight(0xffffff, 0.25);
dirLight.position.set(0, 500, 0);
camera.add(dirLight);
scene.add(camera);

scene.add(globe);

// Controls
const controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableZoom = false;
controls.enablePan = false;
controls.autoRotate = true;
controls.autoRotateSpeed = 1.5;
controls.maxDistance = 1500;
controls.minDistance = 300;
controls.enableDamping = true;
controls.dampingFactor = 0.1;

// Animation loop
function animate() {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
}
animate();

// Resize
const ro = new ResizeObserver(() => {
camera.aspect = width() / height();
camera.updateProjectionMatrix();
renderer.setSize(width(), height());
});
ro.observe(container);

// --- Arc/Point Management ---
function updateGlobe() {
globe.arcsData(arcs);
globe.pointsData(points);
}

function rebuildPoints() {
const pts = [];
if (originLat !== null) {
pts.push({ lat: originLat, lng: originLng, color: COLORS.pointOrigin, id: -1 });
}
arcs.forEach(a => {
if (!a.ghost) {
pts.push({ lat: a.endLat, lng: a.endLng, color: COLORS.pointPeer, id: a.workerIdx });
}
});
points = pts;
}

function calcAltitude(count) {
return ARCH_ALTITUDE_MIN + (count - 1) * ARCH_ALTITUDE_GAP;
}

// --- Message API ---
// Messages from Flutter via postMessage or JavaScript handler
//
// setOrigin: { type: 'setOrigin', lat: number, lng: number }
// addArc: { type: 'addArc', workerIdx: number, endLat: number, endLng: number, country: string, iso: string }
// removeArc: { type: 'removeArc', workerIdx: number }
// clear: { type: 'clear' }
// setTheme: { type: 'setTheme', theme: 'dark' | 'light' }

function handleMessage(data) {
switch (data.type) {
case 'setOrigin':
originLat = data.lat;
originLng = data.lng;
// Update existing arcs with new origin
arcs.forEach(a => { a.startLat = originLat; a.startLng = originLng; });
rebuildPoints();
updateGlobe();
break;

case 'addArc': {
// Remove existing arc for this worker
arcs = arcs.filter(a => a.workerIdx !== data.workerIdx);

const iso = data.iso || 'XX';
countryPeerCounts[iso] = (countryPeerCounts[iso] || 0) + 1;

arcs.push({
startLat: originLat || 0,
startLng: originLng || 0,
endLat: data.endLat,
endLng: data.endLng,
country: data.country || '',
iso: iso,
workerIdx: data.workerIdx,
count: countryPeerCounts[iso],
altitude: calcAltitude(countryPeerCounts[iso]),
ghost: false,
color: [COLORS.arcOrigin, COLORS.arcPeer],
});
rebuildPoints();
updateGlobe();
break;
}

case 'removeArc': {
const removed = arcs.find(a => a.workerIdx === data.workerIdx);
arcs = arcs.filter(a => a.workerIdx !== data.workerIdx);
if (removed) {
const iso = removed.iso;
countryPeerCounts[iso] = Math.max(0, (countryPeerCounts[iso] || 1) - 1);
}
rebuildPoints();
updateGlobe();
break;
}

case 'clear':
arcs = [];
points = [];
countryPeerCounts = {};
updateGlobe();
break;

case 'setTheme': {
const dark = data.theme !== 'light';
globe.globeImageUrl(dark
? 'https://embed.lantern.io/uv-map-dark.png'
: 'https://embed.lantern.io/uv-map.png');
globe.atmosphereColor(dark
? 'rgba(0, 188, 212, 1)'
: 'rgba(0, 122, 124, 1)');
globe.atmosphereAltitude(dark ? 0.25 : 0.2);
break;
}
}
}

// Listen for postMessage from Flutter WebView
window.addEventListener('message', function(e) {
if (e.data && typeof e.data === 'object' && e.data.type) {
handleMessage(e.data);
} else if (typeof e.data === 'string') {
try { handleMessage(JSON.parse(e.data)); } catch(_) {}
}
});

// Also expose a global function for evaluateJavascript calls
window.unboundedGlobe = { handleMessage: handleMessage };

// Start with a demo arc to show the globe is alive
// This will be cleared when real data arrives
setTimeout(function() {
if (arcs.length === 0 && originLat === null) {
// Show a subtle idle animation
originLat = 37.7749;
originLng = -122.4194;
rebuildPoints();
updateGlobe();
}
}, 2000);

})();
</script>
</body>
</html>
42 changes: 32 additions & 10 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,16 @@ go 1.25.4

// replace github.com/getlantern/radiance => ../radiance

// replace github.com/getlantern/broflake => ../unbounded

// replace github.com/getlantern/lantern-server-provisioner => ../lantern-server-provisioner

// replace github.com/sagernet/sing-box => ../sing-box-minimal

replace github.com/enobufs/go-nats => github.com/noahlevenson/go-nats v0.0.0-20230720174341-49df1f749775

replace github.com/quic-go/quic-go => github.com/getlantern/quic-go-unbounded-fork v0.51.3-unbounded

replace github.com/sagernet/sing => github.com/getlantern/sing v0.7.18-lantern

replace github.com/sagernet/sing-box => github.com/getlantern/sing-box-minimal v1.12.19-lantern
Expand All @@ -22,7 +28,7 @@ require (
github.com/Microsoft/go-winio v0.6.2
github.com/alecthomas/assert/v2 v2.3.0
github.com/getlantern/lantern-server-provisioner v0.0.0-20251031121934-8ea031fccfa9
github.com/getlantern/radiance v0.0.0-20260221215045-6049f134d863
github.com/getlantern/radiance v0.0.0-20260224202913-7520e330886d
github.com/sagernet/sing-box v1.12.22
golang.org/x/mobile v0.0.0-20250711185624-d5bb5ecc55c0
golang.org/x/sys v0.40.0
Expand Down Expand Up @@ -156,6 +162,7 @@ require (
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/edsrzf/mmap-go v1.1.0 // indirect
github.com/enobufs/go-nats v0.0.1 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/flynn/noise v1.0.1-0.20220214164934-d803f5c4b0f4 // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
Expand All @@ -164,14 +171,15 @@ require (
github.com/getlantern/algeneva v0.0.0-20250307163401-1824e7b54f52 // indirect
github.com/getlantern/amp v0.0.0-20260113204224-600f8e8dfe5f // indirect
github.com/getlantern/appdir v0.0.0-20250324200952-507a0625eb01 // indirect
github.com/getlantern/common v1.2.1-0.20260121160752-d8ee5791108f // indirect
github.com/getlantern/broflake v0.0.0-20260223195036-4065257e0911 // indirect
github.com/getlantern/common v1.2.1-0.20260223192400-cc00002ef6c7 // indirect
github.com/getlantern/dnstt v0.0.0-20260112160750-05100563bd0d // indirect
github.com/getlantern/fronted v0.0.0-20260219001615-7eabaa834efe // indirect
github.com/getlantern/golog v0.0.0-20230503153817-8e72de7e0a65 // indirect
github.com/getlantern/iptool v0.0.0-20230112135223-c00e863b2696 // indirect
github.com/getlantern/keepcurrent v0.0.0-20240126172110-2e0264ca385d // indirect
github.com/getlantern/kindling v0.0.0-20260219202502-df15c15dc5fb // indirect
github.com/getlantern/lantern-box v0.0.6-0.20260220213333-4b20583e43ff // indirect
github.com/getlantern/lantern-box v0.0.6-0.20260224193811-b50ea1f2e9db // indirect
github.com/getlantern/lantern-water v0.0.0-20260130212632-d5ea08838250 // indirect
github.com/getlantern/mtime v0.0.0-20200417132445-23682092d1f7 // indirect
github.com/getlantern/netx v0.0.0-20240830183145-c257516187f0 // indirect
Expand All @@ -185,6 +193,7 @@ require (
github.com/go-llsqlite/adapter v0.0.0-20230927005056-7f5ce7f0c916 // indirect
github.com/go-llsqlite/crawshaw v0.4.0 // indirect
github.com/go-resty/resty/v2 v2.16.5 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/gobwas/ws v1.4.0 // indirect
github.com/goccy/go-yaml v1.19.0 // indirect
Expand All @@ -193,6 +202,7 @@ require (
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 // indirect
github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/csrf v1.7.3-0.20250123201450-9dd6af1f6d30 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect
Expand Down Expand Up @@ -222,26 +232,37 @@ require (
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/mschoch/smat v0.2.0 // indirect
github.com/nwaples/rardecode v1.1.2 // indirect
github.com/onsi/ginkgo/v2 v2.12.0 // indirect
github.com/pion/datachannel v1.5.10 // indirect
github.com/pion/dtls/v2 v2.2.7 // indirect
github.com/pion/dtls/v2 v2.2.12 // indirect
github.com/pion/dtls/v3 v3.0.6 // indirect
github.com/pion/ice/v2 v2.3.24 // indirect
github.com/pion/interceptor v0.1.37 // indirect
github.com/pion/logging v0.2.3 // indirect
github.com/pion/ice/v4 v4.0.10 // indirect
github.com/pion/interceptor v0.1.40 // indirect
github.com/pion/logging v0.2.4 // indirect
github.com/pion/mdns v0.0.12 // indirect
github.com/pion/mdns/v2 v2.0.7 // indirect
github.com/pion/randutil v0.1.0 // indirect
github.com/pion/rtcp v1.2.15 // indirect
github.com/pion/rtp v1.8.12 // indirect
github.com/pion/sctp v1.8.37 // indirect
github.com/pion/sdp/v3 v3.0.11 // indirect
github.com/pion/rtp v1.8.19 // indirect
github.com/pion/sctp v1.8.39 // indirect
github.com/pion/sdp/v3 v3.0.14 // indirect
github.com/pion/srtp/v2 v2.0.18 // indirect
github.com/pion/srtp/v3 v3.0.6 // indirect
github.com/pion/stun v0.6.1 // indirect
github.com/pion/transport/v2 v2.2.4 // indirect
github.com/pion/stun/v3 v3.0.0 // indirect
github.com/pion/transport v0.14.1 // indirect
github.com/pion/transport/v2 v2.2.10 // indirect
github.com/pion/transport/v3 v3.0.7 // indirect
github.com/pion/turn v1.3.7 // indirect
github.com/pion/turn/v2 v2.1.3 // indirect
github.com/pion/turn/v4 v4.0.2 // indirect
github.com/pion/webrtc/v3 v3.2.40 // indirect
github.com/pion/webrtc/v4 v4.1.2 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus-community/pro-bing v0.4.0 // indirect
github.com/quic-go/quic-go v0.51.0 // indirect
github.com/r3labs/sse/v2 v2.10.0 // indirect
github.com/refraction-networking/utls v1.8.2 // indirect
github.com/refraction-networking/water v0.7.1-alpha // indirect
Expand Down Expand Up @@ -269,6 +290,7 @@ require (
github.com/tjfoc/gmsm v1.4.1 // indirect
github.com/tkuchiki/go-timezone v0.2.0 // indirect
github.com/ulikunitz/xz v0.5.10 // indirect
github.com/wlynxg/anet v0.0.5 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
github.com/xtaci/kcp-go/v5 v5.6.20 // indirect
Expand Down
Loading
Loading