Skip to content

Commit 1810a3e

Browse files
vveerrggclaude
andcommitted
feat: add Bunker URL card to Apps tab in sidepanel
Adds a "Create Bunker URL" card above Permissions in the Apps tab. Users can generate a NIP-46 bunker URL, copy it, and paste it into any Nostr app. Includes security warning: never share publicly. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 0512860 commit 1810a3e

2 files changed

Lines changed: 101 additions & 0 deletions

File tree

src/sidepanel.html

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,10 @@
9090
color: #a6e22e;
9191
margin-bottom: 12px;
9292
}
93+
@keyframes pulse-dot {
94+
0%, 100% { opacity: 1; }
95+
50% { opacity: 0.4; }
96+
}
9397
.input {
9498
width: 100%;
9599
min-height: 44px;
@@ -467,6 +471,31 @@ <h2 class="section-title">Relays</h2>
467471

468472
<!-- PERMISSIONS VIEW -->
469473
<div id="view-permissions" class="view-section">
474+
<!-- Bunker URL card -->
475+
<div class="section-card">
476+
<h2 class="section-title">Bunker URL</h2>
477+
<p style="color:#b0b0a8;font-size:14px;margin-bottom:12px;">Log in to any Nostr app without sharing your keys. Create a bunker URL and paste it wherever NIP-46 is accepted.</p>
478+
<!-- State: ready -->
479+
<div id="bunker-srv-ready">
480+
<button id="btn-bunker-srv-start" class="button w-full">Create Bunker URL</button>
481+
</div>
482+
<!-- State: active -->
483+
<div id="bunker-srv-active" style="display:none;">
484+
<div style="display:flex;align-items:center;gap:8px;margin-bottom:12px;">
485+
<span style="width:8px;height:8px;border-radius:50%;background:#a6e22e;display:inline-block;animation:pulse-dot 2s ease-in-out infinite;"></span>
486+
<span style="color:#a6e22e;font-size:13px;font-weight:600;">Active</span>
487+
</div>
488+
<div style="background:#272822;border:1px solid #49483e;border-radius:8px;padding:10px 12px;margin-bottom:12px;display:flex;align-items:center;gap:8px;">
489+
<code id="bunker-srv-uri" style="flex:1;font-size:11px;color:#a6e22e;word-break:break-all;line-height:1.4;"></code>
490+
<button id="btn-bunker-srv-copy" class="button" style="min-height:32px;padding:4px 12px;font-size:12px;">Copy</button>
491+
</div>
492+
<p style="color:#8f908a;font-size:12px;margin-bottom:8px;">Paste this URL into any Nostr app that supports bunker login.</p>
493+
<p style="color:#f92672;font-size:12px;font-weight:600;margin-bottom:12px;">Never share this URL. Anyone with it can sign events as you.</p>
494+
<button id="btn-bunker-srv-stop" class="button w-full" style="border-color:#f92672;color:#f92672;">Disconnect</button>
495+
</div>
496+
</div>
497+
498+
<!-- Permissions card -->
470499
<div class="section-card">
471500
<h2 class="section-title">Permissions</h2>
472501
<p style="color:#b0b0a8;font-size:14px;margin-bottom:12px;">App permissions for signing requests.</p>

src/sidepanel.js

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,13 @@ function initElements() {
120120
elements.addRelayBtn = $('add-relay-btn');
121121
// Permissions view
122122
elements.permissionsList = $('permissions-list');
123+
// Bunker server
124+
elements.bunkerSrvReady = $('bunker-srv-ready');
125+
elements.bunkerSrvActive = $('bunker-srv-active');
126+
elements.bunkerSrvUri = $('bunker-srv-uri');
127+
elements.btnBunkerSrvStart = $('btn-bunker-srv-start');
128+
elements.btnBunkerSrvCopy = $('btn-bunker-srv-copy');
129+
elements.btnBunkerSrvStop = $('btn-bunker-srv-stop');
123130
// Settings view buttons
124131
elements.openSettingsBtn = $('open-settings-btn');
125132
elements.openHistoryBtn = $('open-history-btn');
@@ -1019,6 +1026,7 @@ async function loadPermissionsView() {
10191026
state.permissions = [];
10201027
renderPermissionsList();
10211028
}
1029+
checkBunkerServerStatus();
10221030
}
10231031

10241032
function renderPermissionsList() {
@@ -1035,6 +1043,59 @@ function renderPermissionsList() {
10351043
`).join('');
10361044
}
10371045

1046+
// Bunker server UI
1047+
async function checkBunkerServerStatus() {
1048+
try {
1049+
const status = await api.runtime.sendMessage({ kind: 'bunkerServer.status' });
1050+
if (status && status.active) {
1051+
elements.bunkerSrvReady.style.display = 'none';
1052+
elements.bunkerSrvActive.style.display = '';
1053+
elements.bunkerSrvUri.textContent = status.uri || '';
1054+
} else {
1055+
elements.bunkerSrvReady.style.display = '';
1056+
elements.bunkerSrvActive.style.display = 'none';
1057+
}
1058+
} catch {
1059+
// Extension may not support it yet
1060+
}
1061+
}
1062+
1063+
async function startBunkerServer() {
1064+
elements.btnBunkerSrvStart.disabled = true;
1065+
elements.btnBunkerSrvStart.textContent = 'Creating\u2026';
1066+
try {
1067+
const result = await api.runtime.sendMessage({
1068+
kind: 'bunkerServer.start',
1069+
payload: { relayUrls: ['wss://relay.nostrkey.com'] },
1070+
});
1071+
if (!result || !result.success) throw new Error(result?.error || 'Failed');
1072+
elements.bunkerSrvUri.textContent = result.uri;
1073+
elements.bunkerSrvReady.style.display = 'none';
1074+
elements.bunkerSrvActive.style.display = '';
1075+
} catch (e) {
1076+
console.error('Bunker start error:', e);
1077+
}
1078+
elements.btnBunkerSrvStart.disabled = false;
1079+
elements.btnBunkerSrvStart.textContent = 'Create Bunker URL';
1080+
}
1081+
1082+
async function stopBunkerServer() {
1083+
try {
1084+
await api.runtime.sendMessage({ kind: 'bunkerServer.stop' });
1085+
} catch {}
1086+
elements.bunkerSrvActive.style.display = 'none';
1087+
elements.bunkerSrvReady.style.display = '';
1088+
}
1089+
1090+
function copyBunkerUri() {
1091+
const uri = elements.bunkerSrvUri.textContent;
1092+
if (!uri) return;
1093+
navigator.clipboard.writeText(uri).then(() => {
1094+
elements.btnBunkerSrvCopy.textContent = 'Copied!';
1095+
setTimeout(() => { elements.btnBunkerSrvCopy.textContent = 'Copy'; }, 1500);
1096+
});
1097+
}
1098+
10381099
// Event bindings
10391100
function bindEvents() {
10401101
elements.unlockForm.addEventListener('submit', async (e) => {
@@ -1167,6 +1228,17 @@ function bindEvents() {
11671228
});
11681229
}
11691230

1231+
// Bunker server
1232+
if (elements.btnBunkerSrvStart) {
1233+
elements.btnBunkerSrvStart.addEventListener('click', startBunkerServer);
1234+
}
1235+
if (elements.btnBunkerSrvStop) {
1236+
elements.btnBunkerSrvStop.addEventListener('click', stopBunkerServer);
1237+
}
1238+
if (elements.btnBunkerSrvCopy) {
1239+
elements.btnBunkerSrvCopy.addEventListener('click', copyBunkerUri);
1240+
}
1241+
11701242
// Vault unlock form
11711243
if (elements.vaultUnlockForm) {
11721244
elements.vaultUnlockForm.addEventListener('submit', async (e) => {

0 commit comments

Comments
 (0)