Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
4 changes: 3 additions & 1 deletion Caddyfile
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
# (no content hashing yet — don't let stale JS strand logged-in users).
@images path *.svg *.png *.ico
header @images Cache-Control "public, max-age=86400"
@code path *.js *.css *.html / /privacy /login /settings /dns
@code path *.js *.css *.html / /privacy /login /settings /dns /push
header @code Cache-Control "public, max-age=60, must-revalidate"

encode gzip zstd
Expand All @@ -66,6 +66,8 @@
rewrite @settings /settings.html
@dns path /dns
rewrite @dns /dns.html
@push path /push
rewrite @push /push.html

file_server

Expand Down
4 changes: 4 additions & 0 deletions apps/desktop/extensions/ai-sidebar/background.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { decideInstallTarget, lookupInstalled } from './install-state.js';
import { PIT_SOCKS_PORT, pitProxyConfig } from './pit-proxy.js';
import { installPush } from './push-client.js';

// Web Push through TronBrowser's configured push service (the engine has none).
installPush();

// Open the AI side panel when the toolbar action is clicked.
chrome.sidePanel
Expand Down
27 changes: 26 additions & 1 deletion apps/desktop/extensions/ai-sidebar/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@
"scripting",
"proxy",
"privacy",
"notifications"
"notifications",
"alarms",
"contentSettings"
],
"host_permissions": [
"https://api.openai.com/*",
Expand All @@ -40,6 +42,29 @@
"type": "module"
},
"content_scripts": [
{
"matches": [
"https://*/*",
"http://localhost/*",
"http://127.0.0.1/*"
],
"js": [
"push-page.js"
],
"run_at": "document_start",
"world": "MAIN"
},
{
"matches": [
"https://*/*",
"http://localhost/*",
"http://127.0.0.1/*"
],
"js": [
"push-bridge.js"
],
"run_at": "document_start"
},
{
"matches": [
"https://chromewebstore.google.com/detail/*",
Expand Down
19 changes: 19 additions & 0 deletions apps/desktop/extensions/ai-sidebar/options.html
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,25 @@ <h2>Name resolution</h2>
name still needs the certificate that <code>moshcode dns enable</code> installs.
</p>

<h2>Push notifications</h2>
<p class="hint">
Sites send browser notifications through a push service the browser chooses.
The engine TronBrowser runs on ships without one, so TronBrowser brings its
own. Messages reach it encrypted to a key only this browser holds.
</p>
<label for="pushMode">Push service</label>
<select id="pushMode">
<option value="default">TronBrowser (tronbrowser.dev/api/1/push)</option>
<option value="custom">Another push service…</option>
<option value="off">Off (the engine's own behaviour)</option>
</select>
<div id="pushCustomRow" hidden>
<label for="pushUrl">Push service URL</label>
<input id="pushUrl" placeholder="https://push.example.com/api/1/push" />
</div>
<button id="savePush">Save</button><span id="savedPush" class="saved"></span>
<p class="hint">Changing it signs sites out of push; they subscribe again the next time they ask.</p>

<h2>AI providers (bring your own keys)</h2>
<p class="hint">Add keys for as many providers as you like, then pick a default for
the sidebar. Keys are stored on your account (encrypted end-to-end when a vault
Expand Down
37 changes: 37 additions & 0 deletions apps/desktop/extensions/ai-sidebar/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
} from "./coinpay-auth.js";
import { pushSettings, pullSettings } from "./settings-store.js";
import { encryptVault, decryptVault } from "./vault.js";
import { DEFAULT_PUSH_SERVICE, pushServiceFrom } from "./push-client.js";
import {
connect as btrConnect,
disconnect as btrDisconnect,
Expand Down Expand Up @@ -391,6 +392,42 @@ function flash(id, msg) {
el(id).textContent = msg;
setTimeout(() => (el(id).textContent = ""), 1600);
}

/* ---------- Push service (push-client.js reads `pushService`) ---------- */
async function mountPush() {
const { pushService } = await chrome.storage.local.get("pushService");
const mode = pushService === "off" ? "off" : pushService && pushService !== DEFAULT_PUSH_SERVICE ? "custom" : "default";
el("pushMode").value = mode;
el("pushUrl").value = mode === "custom" ? pushService : "";
el("pushCustomRow").hidden = mode !== "custom";
el("pushMode").addEventListener("change", () => {
el("pushCustomRow").hidden = el("pushMode").value !== "custom";
});
el("savePush").addEventListener("click", async () => {
const choice = el("pushMode").value;
if (choice === "off") {
await chrome.storage.local.set({ pushService: "off" });
return flash("savedPush", "push off ✓");
}
if (choice === "default") {
await chrome.storage.local.remove("pushService");
return flash("savedPush", "saved ✓");
}
const url = pushServiceFrom(el("pushUrl").value);
if (!url || url === DEFAULT_PUSH_SERVICE) return flash("savedPush", "enter an https:// URL");
// Check it speaks our protocol before switching every site over to it.
try {
const info = await (await fetch(url)).json();
if (info.service !== "tronbrowser-push") throw new Error("not a compatible push service");
} catch (e) {
return flash("savedPush", `can't use that URL: ${e.message}`);
}
await chrome.storage.local.set({ pushService: url });
flash("savedPush", "saved ✓");
});
}
mountPush();

function escape(s) {
const d = document.createElement("div");
d.textContent = s || "";
Expand Down
26 changes: 26 additions & 0 deletions apps/desktop/extensions/ai-sidebar/push-bridge.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Isolated-world half of the pushManager replacement: forwards push-page.js's
// requests to the background and hands the answers back. The background reads
// the origin from Chrome's sender info, so nothing the page sends here can act
// for another site.
(() => {
if (window.top !== window) return;
const TYPES = new Set(['push:subscribe', 'push:get', 'push:unsubscribe']);
window.addEventListener('message', (event) => {
const data = event.data;
if (event.source !== window || data?.__tronPush !== 'request' || !TYPES.has(data.type)) return;
const reply = (result) =>
window.postMessage({ __tronPush: 'response', id: data.id, result }, location.origin);
try {
chrome.runtime.sendMessage(
{ type: data.type, scope: typeof data.scope === 'string' ? data.scope : undefined, applicationServerKey: data.applicationServerKey ?? null },
(result) => {
if (chrome.runtime.lastError) reply({ error: 'AbortError', message: 'Registration failed - push service error' });
else reply(result);
},
);
} catch {
// The extension was reloaded under this page; behave as if push is off.
reply({ off: true });
}
});
})();
Loading
Loading