Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
b535a7b967
|
|||
|
abd4a21f8f
|
@@ -42,25 +42,33 @@ This project uses **standard semver with `v` prefix** (e.g., `v0.0.8`, `v1.2.3`)
|
||||
```
|
||||
If any step fails, fix issues before proceeding.
|
||||
|
||||
6. **Compose a commit message** following this format:
|
||||
6. **Create release zip files** in the `releases/` folder:
|
||||
```
|
||||
mkdir -p releases
|
||||
cd dist/chrome && zip -r ../../releases/plebeian-signer-chrome-vX.Y.Z.zip . && cd ../..
|
||||
cd dist/firefox && zip -r ../../releases/plebeian-signer-firefox-vX.Y.Z.zip . && cd ../..
|
||||
```
|
||||
Replace `vX.Y.Z` with the actual version number.
|
||||
|
||||
7. **Compose a commit message** following this format:
|
||||
- First line: 72 chars max, imperative mood summary (e.g., "Release v0.0.8")
|
||||
- Blank line
|
||||
- Bullet points describing each significant change
|
||||
- "Files modified:" section listing affected files
|
||||
- Footer with Claude Code attribution
|
||||
|
||||
7. **Stage all changes** with `git add -A`
|
||||
8. **Stage all changes** with `git add -A`
|
||||
|
||||
8. **Create the commit** with the composed message
|
||||
9. **Create the commit** with the composed message
|
||||
|
||||
9. **Create a git tag** matching the version (e.g., `v0.0.8`)
|
||||
10. **Create a git tag** matching the version (e.g., `v0.0.8`)
|
||||
|
||||
10. **Push to origin** with tags:
|
||||
11. **Push to origin** with tags:
|
||||
```
|
||||
git push origin main --tags
|
||||
```
|
||||
|
||||
11. **Report completion** with the new version and commit hash
|
||||
12. **Report completion** with the new version and commit hash
|
||||
|
||||
## Important:
|
||||
- This is a browser extension with separate Chrome and Firefox builds
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "plebeian-signer",
|
||||
"version": "v1.0.1",
|
||||
"version": "v1.0.3",
|
||||
"custom": {
|
||||
"chrome": {
|
||||
"version": "v1.0.1"
|
||||
"version": "v1.0.3"
|
||||
},
|
||||
"firefox": {
|
||||
"version": "v1.0.1"
|
||||
"version": "v1.0.3"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"manifest_version": 3,
|
||||
"name": "Plebeian Signer - Nostr Identity Manager & Signer",
|
||||
"description": "Manage and switch between multiple identities while interacting with Nostr apps",
|
||||
"version": "1.0.1",
|
||||
"version": "1.0.3",
|
||||
"homepage_url": "https://git.mleku.dev/mleku/plebeian-signer",
|
||||
"options_page": "options.html",
|
||||
"permissions": [
|
||||
|
||||
|
Before Width: | Height: | Size: 983 B After Width: | Height: | Size: 983 B |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
@@ -226,7 +226,7 @@
|
||||
<!------------->
|
||||
<div class="sam-footer-grid-2">
|
||||
<div class="btn-group">
|
||||
<button id="rejectButton" type="button" class="btn btn-secondary">
|
||||
<button id="rejectOnceButton" type="button" class="btn btn-secondary">
|
||||
Reject
|
||||
</button>
|
||||
<button
|
||||
@@ -239,16 +239,16 @@
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-end">
|
||||
<li>
|
||||
<button id="rejectJustOnceButton" class="dropdown-item">
|
||||
just once
|
||||
<button id="rejectAlwaysButton" class="dropdown-item">
|
||||
Reject Always
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="btn-group">
|
||||
<button id="approveButton" type="button" class="btn btn-primary">
|
||||
Approve
|
||||
<button id="approveAlwaysButton" type="button" class="btn btn-primary">
|
||||
Approve Always
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@@ -260,8 +260,8 @@
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li>
|
||||
<button id="approveJustOnceButton" class="dropdown-item" href="#">
|
||||
just once
|
||||
<button id="approveOnceButton" class="dropdown-item">
|
||||
Approve Once
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -9,6 +9,7 @@ import { IdentitiesComponent } from './components/home/identities/identities.com
|
||||
import { IdentityComponent } from './components/home/identity/identity.component';
|
||||
import { InfoComponent } from './components/home/info/info.component';
|
||||
import { SettingsComponent } from './components/home/settings/settings.component';
|
||||
import { LogsComponent } from './components/home/logs/logs.component';
|
||||
import { NewIdentityComponent } from './components/new-identity/new-identity.component';
|
||||
import { EditIdentityComponent } from './components/edit-identity/edit-identity.component';
|
||||
import { HomeComponent as EditIdentityHomeComponent } from './components/edit-identity/home/home.component';
|
||||
@@ -66,6 +67,10 @@ export const routes: Routes = [
|
||||
path: 'settings',
|
||||
component: SettingsComponent,
|
||||
},
|
||||
{
|
||||
path: 'logs',
|
||||
component: LogsComponent,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -7,7 +7,12 @@
|
||||
<span class="text-muted" style="font-size: 12px">
|
||||
Nothing configured so far.
|
||||
</span>
|
||||
} @for(hostPermissions of hostsPermissions; track hostPermissions) {
|
||||
} @else {
|
||||
<button class="btn btn-danger btn-sm remove-all-btn" (click)="onClickRemoveAllPermissions()">
|
||||
Remove All Permissions
|
||||
</button>
|
||||
}
|
||||
@for(hostPermissions of hostsPermissions; track hostPermissions) {
|
||||
<div class="permissions-card">
|
||||
<span style="margin-bottom: 4px; font-weight: 500">
|
||||
{{ hostPermissions.host }}
|
||||
|
||||
@@ -17,6 +17,10 @@
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.remove-all-btn {
|
||||
margin-bottom: var(--size);
|
||||
}
|
||||
|
||||
.permissions-card {
|
||||
background: var(--background-light);
|
||||
border-radius: 8px;
|
||||
|
||||
@@ -41,6 +41,14 @@ export class PermissionsComponent extends NavComponent implements OnInit {
|
||||
this.#buildHostsPermissions(this.identity?.id);
|
||||
}
|
||||
|
||||
async onClickRemoveAllPermissions() {
|
||||
const allPermissions = this.hostsPermissions.flatMap(hp => hp.permissions);
|
||||
for (const permission of allPermissions) {
|
||||
await this.#storage.deletePermission(permission.id);
|
||||
}
|
||||
this.#buildHostsPermissions(this.identity?.id);
|
||||
}
|
||||
|
||||
#initialize(identityId: string) {
|
||||
this.identity = this.#storage
|
||||
.getBrowserSessionHandler()
|
||||
|
||||
@@ -33,4 +33,8 @@
|
||||
<a class="tab" routerLink="/home/info" routerLinkActive="active" title="Info">
|
||||
<i class="bi bi-info-circle"></i>
|
||||
</a>
|
||||
|
||||
<a class="tab" routerLink="/home/logs" routerLinkActive="active" title="Logs">
|
||||
<span style="font-size: 1.2rem">🪵</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
<div class="logs-header">
|
||||
<span class="logs-title">Logs</span>
|
||||
<button class="btn btn-sm btn-secondary" (click)="onClear()">Clear Log</button>
|
||||
</div>
|
||||
|
||||
<div class="logs-container">
|
||||
@if (logs.length === 0) {
|
||||
<div class="logs-empty">No logs yet</div>
|
||||
}
|
||||
@for (log of logs; track log.timestamp) {
|
||||
<div class="log-entry" [class]="getLevelClass(log.level)">
|
||||
<span class="log-time">{{ log.timestamp | date:'HH:mm:ss' }}</span>
|
||||
<span class="log-level">{{ log.level }}</span>
|
||||
<span class="log-message">{{ log.message }}</span>
|
||||
@if (log.data) {
|
||||
<pre class="log-data">{{ log.data | json }}</pre>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@@ -0,0 +1,93 @@
|
||||
:host {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: var(--size);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.logs-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: var(--size);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.logs-title {
|
||||
font-weight: 600;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.logs-container {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
background: var(--background-light);
|
||||
border-radius: 8px;
|
||||
padding: var(--size-h);
|
||||
}
|
||||
|
||||
.logs-empty {
|
||||
color: var(--muted-foreground);
|
||||
text-align: center;
|
||||
padding: var(--size);
|
||||
}
|
||||
|
||||
.log-entry {
|
||||
font-family: monospace;
|
||||
font-size: 11px;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 2px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
align-items: flex-start;
|
||||
|
||||
&.log-error {
|
||||
background: rgba(220, 53, 69, 0.15);
|
||||
color: #ff6b6b;
|
||||
}
|
||||
|
||||
&.log-warn {
|
||||
background: rgba(255, 193, 7, 0.15);
|
||||
color: #ffc107;
|
||||
}
|
||||
|
||||
&.log-debug {
|
||||
background: rgba(108, 117, 125, 0.15);
|
||||
color: #adb5bd;
|
||||
}
|
||||
|
||||
&.log-info {
|
||||
background: rgba(13, 110, 253, 0.1);
|
||||
color: var(--foreground);
|
||||
}
|
||||
}
|
||||
|
||||
.log-time {
|
||||
color: var(--muted-foreground);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.log-level {
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
width: 40px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.log-message {
|
||||
flex: 1;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.log-data {
|
||||
width: 100%;
|
||||
margin: 4px 0 0 0;
|
||||
padding: 4px;
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
border-radius: 4px;
|
||||
font-size: 10px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { Component, inject } from '@angular/core';
|
||||
import { LoggerService, LogEntry } from '@common';
|
||||
import { DatePipe, JsonPipe } from '@angular/common';
|
||||
|
||||
@Component({
|
||||
selector: 'app-logs',
|
||||
templateUrl: './logs.component.html',
|
||||
styleUrl: './logs.component.scss',
|
||||
imports: [DatePipe, JsonPipe],
|
||||
})
|
||||
export class LogsComponent {
|
||||
readonly #logger = inject(LoggerService);
|
||||
|
||||
get logs(): LogEntry[] {
|
||||
return this.#logger.logs;
|
||||
}
|
||||
|
||||
onClear() {
|
||||
this.#logger.clear();
|
||||
}
|
||||
|
||||
getLevelClass(level: LogEntry['level']): string {
|
||||
switch (level) {
|
||||
case 'error':
|
||||
return 'log-error';
|
||||
case 'warn':
|
||||
return 'log-warn';
|
||||
case 'debug':
|
||||
return 'log-debug';
|
||||
default:
|
||||
return 'log-info';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
} from '@common';
|
||||
import { ChromeMetaHandler } from './app/common/data/chrome-meta-handler';
|
||||
import { Event, EventTemplate, finalizeEvent, nip04, nip44 } from 'nostr-tools';
|
||||
import { Buffer } from 'buffer';
|
||||
|
||||
export const debug = function (message: any) {
|
||||
const dateString = new Date().toISOString();
|
||||
@@ -66,6 +67,8 @@ export const shouldRecklessModeApprove = async function (
|
||||
host: string
|
||||
): Promise<boolean> {
|
||||
const signerMetaData = await getSignerMetaData();
|
||||
debug(`shouldRecklessModeApprove: recklessMode=${signerMetaData.recklessMode}, host=${host}`);
|
||||
debug(`Full signerMetaData: ${JSON.stringify(signerMetaData)}`);
|
||||
|
||||
if (!signerMetaData.recklessMode) {
|
||||
return false;
|
||||
@@ -223,8 +226,7 @@ export const storePermission = async function (
|
||||
// Encrypt permission to store in sync storage (depending on sync flow).
|
||||
const encryptedPermission = await encryptPermission(
|
||||
permission,
|
||||
browserSessionData.iv,
|
||||
browserSessionData.vaultPassword as string
|
||||
browserSessionData
|
||||
);
|
||||
|
||||
await savePermissionsToBrowserSyncStorage([
|
||||
@@ -321,22 +323,20 @@ export const nip44Decrypt = async function (
|
||||
|
||||
const encryptPermission = async function (
|
||||
permission: Permission_DECRYPTED,
|
||||
iv: string,
|
||||
password: string
|
||||
sessionData: BrowserSessionData
|
||||
): Promise<Permission_ENCRYPTED> {
|
||||
const encryptedPermission: Permission_ENCRYPTED = {
|
||||
id: await encrypt(permission.id, iv, password),
|
||||
identityId: await encrypt(permission.identityId, iv, password),
|
||||
host: await encrypt(permission.host, iv, password),
|
||||
method: await encrypt(permission.method, iv, password),
|
||||
methodPolicy: await encrypt(permission.methodPolicy, iv, password),
|
||||
id: await encrypt(permission.id, sessionData),
|
||||
identityId: await encrypt(permission.identityId, sessionData),
|
||||
host: await encrypt(permission.host, sessionData),
|
||||
method: await encrypt(permission.method, sessionData),
|
||||
methodPolicy: await encrypt(permission.methodPolicy, sessionData),
|
||||
};
|
||||
|
||||
if (typeof permission.kind !== 'undefined') {
|
||||
encryptedPermission.kind = await encrypt(
|
||||
permission.kind.toString(),
|
||||
iv,
|
||||
password
|
||||
sessionData
|
||||
);
|
||||
}
|
||||
|
||||
@@ -345,8 +345,30 @@ const encryptPermission = async function (
|
||||
|
||||
const encrypt = async function (
|
||||
value: string,
|
||||
iv: string,
|
||||
password: string
|
||||
sessionData: BrowserSessionData
|
||||
): Promise<string> {
|
||||
return await CryptoHelper.encrypt(value, iv, password);
|
||||
// v2: Use pre-derived key with AES-GCM directly
|
||||
if (sessionData.vaultKey) {
|
||||
const keyBytes = Buffer.from(sessionData.vaultKey, 'base64');
|
||||
const iv = Buffer.from(sessionData.iv, 'base64');
|
||||
|
||||
const key = await crypto.subtle.importKey(
|
||||
'raw',
|
||||
keyBytes,
|
||||
{ name: 'AES-GCM' },
|
||||
false,
|
||||
['encrypt']
|
||||
);
|
||||
|
||||
const cipherText = await crypto.subtle.encrypt(
|
||||
{ name: 'AES-GCM', iv },
|
||||
key,
|
||||
new TextEncoder().encode(value)
|
||||
);
|
||||
|
||||
return Buffer.from(cipherText).toString('base64');
|
||||
}
|
||||
|
||||
// v1: Use password with PBKDF2
|
||||
return await CryptoHelper.encrypt(value, sessionData.iv, sessionData.vaultPassword!);
|
||||
};
|
||||
|
||||
@@ -67,6 +67,7 @@ browser.runtime.onMessage.addListener(async (message /*, sender*/) => {
|
||||
|
||||
// Check reckless mode first
|
||||
const recklessApprove = await shouldRecklessModeApprove(req.host);
|
||||
debug(`recklessApprove result: ${recklessApprove}`);
|
||||
if (recklessApprove) {
|
||||
debug('Request auto-approved via reckless mode.');
|
||||
} else {
|
||||
@@ -78,6 +79,7 @@ browser.runtime.onMessage.addListener(async (message /*, sender*/) => {
|
||||
req.method,
|
||||
req.params
|
||||
);
|
||||
debug(`permissionState result: ${permissionState}`);
|
||||
|
||||
if (permissionState === false) {
|
||||
throw new Error('Permission denied');
|
||||
|
||||
@@ -1,14 +1,32 @@
|
||||
import browser from 'webextension-polyfill';
|
||||
import { Buffer } from 'buffer';
|
||||
import { Nip07Method } from '@common';
|
||||
import { PromptResponse, PromptResponseMessage } from './background-common';
|
||||
|
||||
/**
|
||||
* Decode base64 string to UTF-8 using native browser APIs.
|
||||
* This avoids race conditions with the Buffer polyfill initialization.
|
||||
*/
|
||||
function base64ToUtf8(base64: string): string {
|
||||
const binaryString = atob(base64);
|
||||
const bytes = Uint8Array.from(binaryString, char => char.charCodeAt(0));
|
||||
return new TextDecoder('utf-8').decode(bytes);
|
||||
}
|
||||
|
||||
const params = new URLSearchParams(location.search);
|
||||
const id = params.get('id') as string;
|
||||
const method = params.get('method') as Nip07Method;
|
||||
const host = params.get('host') as string;
|
||||
const nick = params.get('nick') as string;
|
||||
const event = Buffer.from(params.get('event') as string, 'base64').toString();
|
||||
|
||||
let event = '{}';
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
let eventParsed: any = {};
|
||||
try {
|
||||
event = base64ToUtf8(params.get('event') as string);
|
||||
eventParsed = JSON.parse(event);
|
||||
} catch (e) {
|
||||
console.error('Failed to parse event:', e);
|
||||
}
|
||||
|
||||
let title = '';
|
||||
switch (method) {
|
||||
@@ -62,8 +80,8 @@ Array.from(document.getElementsByClassName('host-INSERT')).forEach(
|
||||
);
|
||||
|
||||
const kindSpanElement = document.getElementById('kindSpan');
|
||||
if (kindSpanElement) {
|
||||
kindSpanElement.innerText = JSON.parse(event).kind;
|
||||
if (kindSpanElement && eventParsed.kind !== undefined) {
|
||||
kindSpanElement.innerText = eventParsed.kind;
|
||||
}
|
||||
|
||||
const cardGetPublicKeyElement = document.getElementById('cardGetPublicKey');
|
||||
@@ -108,9 +126,8 @@ if (cardNip04EncryptElement && card2Nip04EncryptElement) {
|
||||
'card2Nip04Encrypt_text'
|
||||
);
|
||||
if (card2Nip04Encrypt_textElement) {
|
||||
const eventObject: { peerPubkey: string; plaintext: string } =
|
||||
JSON.parse(event);
|
||||
card2Nip04Encrypt_textElement.innerText = eventObject.plaintext;
|
||||
const eventObject = eventParsed as { peerPubkey: string; plaintext: string };
|
||||
card2Nip04Encrypt_textElement.innerText = eventObject.plaintext || '';
|
||||
}
|
||||
} else {
|
||||
cardNip04EncryptElement.style.display = 'none';
|
||||
@@ -126,9 +143,8 @@ if (cardNip44EncryptElement && card2Nip44EncryptElement) {
|
||||
'card2Nip44Encrypt_text'
|
||||
);
|
||||
if (card2Nip44Encrypt_textElement) {
|
||||
const eventObject: { peerPubkey: string; plaintext: string } =
|
||||
JSON.parse(event);
|
||||
card2Nip44Encrypt_textElement.innerText = eventObject.plaintext;
|
||||
const eventObject = eventParsed as { peerPubkey: string; plaintext: string };
|
||||
card2Nip44Encrypt_textElement.innerText = eventObject.plaintext || '';
|
||||
}
|
||||
} else {
|
||||
cardNip44EncryptElement.style.display = 'none';
|
||||
@@ -143,9 +159,8 @@ if (cardNip04DecryptElement && card2Nip04DecryptElement) {
|
||||
'card2Nip04Decrypt_text'
|
||||
);
|
||||
if (card2Nip04Decrypt_textElement) {
|
||||
const eventObject: { peerPubkey: string; ciphertext: string } =
|
||||
JSON.parse(event);
|
||||
card2Nip04Decrypt_textElement.innerText = eventObject.ciphertext;
|
||||
const eventObject = eventParsed as { peerPubkey: string; ciphertext: string };
|
||||
card2Nip04Decrypt_textElement.innerText = eventObject.ciphertext || '';
|
||||
}
|
||||
} else {
|
||||
cardNip04DecryptElement.style.display = 'none';
|
||||
@@ -161,9 +176,8 @@ if (cardNip44DecryptElement && card2Nip44DecryptElement) {
|
||||
'card2Nip44Decrypt_text'
|
||||
);
|
||||
if (card2Nip44Decrypt_textElement) {
|
||||
const eventObject: { peerPubkey: string; ciphertext: string } =
|
||||
JSON.parse(event);
|
||||
card2Nip44Decrypt_textElement.innerText = eventObject.ciphertext;
|
||||
const eventObject = eventParsed as { peerPubkey: string; ciphertext: string };
|
||||
card2Nip44Decrypt_textElement.innerText = eventObject.ciphertext || '';
|
||||
}
|
||||
} else {
|
||||
cardNip44DecryptElement.style.display = 'none';
|
||||
@@ -175,36 +189,38 @@ if (cardNip44DecryptElement && card2Nip44DecryptElement) {
|
||||
// Functions
|
||||
//
|
||||
|
||||
function deliver(response: PromptResponse) {
|
||||
async function deliver(response: PromptResponse) {
|
||||
const message: PromptResponseMessage = {
|
||||
id,
|
||||
response,
|
||||
};
|
||||
|
||||
browser.runtime.sendMessage(message);
|
||||
try {
|
||||
await browser.runtime.sendMessage(message);
|
||||
} catch (error) {
|
||||
console.error('Failed to send message:', error);
|
||||
}
|
||||
window.close();
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const rejectJustOnceButton = document.getElementById('rejectJustOnceButton');
|
||||
rejectJustOnceButton?.addEventListener('click', () => {
|
||||
const rejectOnceButton = document.getElementById('rejectOnceButton');
|
||||
rejectOnceButton?.addEventListener('click', () => {
|
||||
deliver('reject-once');
|
||||
});
|
||||
|
||||
const rejectButton = document.getElementById('rejectButton');
|
||||
rejectButton?.addEventListener('click', () => {
|
||||
const rejectAlwaysButton = document.getElementById('rejectAlwaysButton');
|
||||
rejectAlwaysButton?.addEventListener('click', () => {
|
||||
deliver('reject');
|
||||
});
|
||||
|
||||
const approveJustOnceButton = document.getElementById(
|
||||
'approveJustOnceButton'
|
||||
);
|
||||
approveJustOnceButton?.addEventListener('click', () => {
|
||||
const approveOnceButton = document.getElementById('approveOnceButton');
|
||||
approveOnceButton?.addEventListener('click', () => {
|
||||
deliver('approve-once');
|
||||
});
|
||||
|
||||
const approveButton = document.getElementById('approveButton');
|
||||
approveButton?.addEventListener('click', () => {
|
||||
const approveAlwaysButton = document.getElementById('approveAlwaysButton');
|
||||
approveAlwaysButton?.addEventListener('click', () => {
|
||||
deliver('approve');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
<div class="deriving-modal">
|
||||
<div class="deriving-spinner"></div>
|
||||
<h3>{{ message }}</h3>
|
||||
<div class="deriving-timer">{{ elapsed.toFixed(1) }}s</div>
|
||||
<p class="deriving-note">This may take 3-6 seconds for security</p>
|
||||
<p class="deriving-note">This may take a few seconds</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -30,14 +30,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.deriving-timer {
|
||||
font-size: 2.5rem;
|
||||
font-weight: bold;
|
||||
color: #ff3eb5;
|
||||
font-family: monospace;
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
.deriving-note {
|
||||
margin: 0.5rem 0 0;
|
||||
color: #a1a1a1;
|
||||
|
||||
@@ -1,23 +1,16 @@
|
||||
import {
|
||||
Component,
|
||||
OnDestroy,
|
||||
} from '@angular/core';
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-deriving-modal',
|
||||
templateUrl: './deriving-modal.component.html',
|
||||
styleUrl: './deriving-modal.component.scss',
|
||||
})
|
||||
export class DerivingModalComponent implements OnDestroy {
|
||||
export class DerivingModalComponent {
|
||||
visible = false;
|
||||
elapsed = 0;
|
||||
message = 'Deriving encryption key';
|
||||
|
||||
#startTime: number | null = null;
|
||||
#animationFrame: number | null = null;
|
||||
|
||||
/**
|
||||
* Show the deriving modal and start the timer
|
||||
* Show the deriving modal
|
||||
* @param message Optional custom message
|
||||
*/
|
||||
show(message?: string): void {
|
||||
@@ -25,35 +18,12 @@ export class DerivingModalComponent implements OnDestroy {
|
||||
this.message = message;
|
||||
}
|
||||
this.visible = true;
|
||||
this.elapsed = 0;
|
||||
this.#startTime = performance.now();
|
||||
this.#updateTimer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the modal and stop the timer
|
||||
* Hide the modal
|
||||
*/
|
||||
hide(): void {
|
||||
this.visible = false;
|
||||
this.#stopTimer();
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.#stopTimer();
|
||||
}
|
||||
|
||||
#updateTimer(): void {
|
||||
if (this.#startTime !== null) {
|
||||
this.elapsed = (performance.now() - this.#startTime) / 1000;
|
||||
this.#animationFrame = requestAnimationFrame(() => this.#updateTimer());
|
||||
}
|
||||
}
|
||||
|
||||
#stopTimer(): void {
|
||||
this.#startTime = null;
|
||||
if (this.#animationFrame !== null) {
|
||||
cancelAnimationFrame(this.#animationFrame);
|
||||
this.#animationFrame = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,76 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
export interface LogEntry {
|
||||
timestamp: Date;
|
||||
level: 'log' | 'warn' | 'error' | 'debug';
|
||||
message: string;
|
||||
data?: any;
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class LoggerService {
|
||||
#namespace: string | undefined;
|
||||
#logs: LogEntry[] = [];
|
||||
#maxLogs = 500;
|
||||
|
||||
get logs(): LogEntry[] {
|
||||
return this.#logs;
|
||||
}
|
||||
|
||||
initialize(namespace: string): void {
|
||||
this.#namespace = namespace;
|
||||
}
|
||||
|
||||
log(value: any) {
|
||||
log(value: any, data?: any) {
|
||||
this.#assureInitialized();
|
||||
|
||||
this.#addLog('log', value, data);
|
||||
const nowString = new Date().toLocaleString();
|
||||
|
||||
console.log(`[${this.#namespace} - ${nowString}]`, JSON.stringify(value));
|
||||
}
|
||||
|
||||
warn(value: any, data?: any) {
|
||||
this.#assureInitialized();
|
||||
this.#addLog('warn', value, data);
|
||||
const nowString = new Date().toLocaleString();
|
||||
console.warn(`[${this.#namespace} - ${nowString}]`, JSON.stringify(value));
|
||||
}
|
||||
|
||||
error(value: any, data?: any) {
|
||||
this.#assureInitialized();
|
||||
this.#addLog('error', value, data);
|
||||
const nowString = new Date().toLocaleString();
|
||||
console.error(`[${this.#namespace} - ${nowString}]`, JSON.stringify(value));
|
||||
}
|
||||
|
||||
debug(value: any, data?: any) {
|
||||
this.#assureInitialized();
|
||||
this.#addLog('debug', value, data);
|
||||
const nowString = new Date().toLocaleString();
|
||||
console.debug(`[${this.#namespace} - ${nowString}]`, JSON.stringify(value));
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.#logs = [];
|
||||
}
|
||||
|
||||
#addLog(level: LogEntry['level'], message: any, data?: any) {
|
||||
const entry: LogEntry = {
|
||||
timestamp: new Date(),
|
||||
level,
|
||||
message: typeof message === 'string' ? message : JSON.stringify(message),
|
||||
data,
|
||||
};
|
||||
this.#logs.unshift(entry);
|
||||
|
||||
// Limit stored logs
|
||||
if (this.#logs.length > this.#maxLogs) {
|
||||
this.#logs.pop();
|
||||
}
|
||||
}
|
||||
|
||||
#assureInitialized() {
|
||||
if (!this.#namespace) {
|
||||
throw new Error(
|
||||
|
||||
@@ -146,12 +146,17 @@ export const decryptPermissions = async function (
|
||||
const decryptedPermissions: Permission_DECRYPTED[] = [];
|
||||
|
||||
for (const permission of permissions) {
|
||||
const decryptedPermission = await decryptPermission.call(
|
||||
this,
|
||||
permission,
|
||||
withLockedVault
|
||||
);
|
||||
decryptedPermissions.push(decryptedPermission);
|
||||
try {
|
||||
const decryptedPermission = await decryptPermission.call(
|
||||
this,
|
||||
permission,
|
||||
withLockedVault
|
||||
);
|
||||
decryptedPermissions.push(decryptedPermission);
|
||||
} catch (error) {
|
||||
// Skip corrupted permissions (e.g., encrypted with wrong key)
|
||||
console.warn('[vault] Skipping corrupted permission:', error);
|
||||
}
|
||||
}
|
||||
|
||||
return decryptedPermissions;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"manifest_version": 3,
|
||||
"name": "Plebeian Signer",
|
||||
"description": "Nostr Identity Manager & Signer",
|
||||
"version": "1.0.1",
|
||||
"version": "1.0.3",
|
||||
"homepage_url": "https://git.mleku.dev/mleku/plebeian-signer",
|
||||
"options_page": "options.html",
|
||||
"permissions": [
|
||||
|
||||
|
Before Width: | Height: | Size: 983 B After Width: | Height: | Size: 983 B |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
@@ -226,7 +226,7 @@
|
||||
<!------------->
|
||||
<div class="sam-footer-grid-2">
|
||||
<div class="btn-group">
|
||||
<button id="rejectButton" type="button" class="btn btn-secondary">
|
||||
<button id="rejectOnceButton" type="button" class="btn btn-secondary">
|
||||
Reject
|
||||
</button>
|
||||
<button
|
||||
@@ -239,16 +239,16 @@
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-end">
|
||||
<li>
|
||||
<button id="rejectJustOnceButton" class="dropdown-item">
|
||||
just once
|
||||
<button id="rejectAlwaysButton" class="dropdown-item">
|
||||
Reject Always
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="btn-group">
|
||||
<button id="approveButton" type="button" class="btn btn-primary">
|
||||
Approve
|
||||
<button id="approveAlwaysButton" type="button" class="btn btn-primary">
|
||||
Approve Always
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@@ -260,8 +260,8 @@
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li>
|
||||
<button id="approveJustOnceButton" class="dropdown-item" href="#">
|
||||
just once
|
||||
<button id="approveOnceButton" class="dropdown-item">
|
||||
Approve Once
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -6,6 +6,7 @@ import { IdentitiesComponent } from './components/home/identities/identities.com
|
||||
import { IdentityComponent } from './components/home/identity/identity.component';
|
||||
import { InfoComponent } from './components/home/info/info.component';
|
||||
import { SettingsComponent } from './components/home/settings/settings.component';
|
||||
import { LogsComponent } from './components/home/logs/logs.component';
|
||||
import { NewIdentityComponent } from './components/new-identity/new-identity.component';
|
||||
import { EditIdentityComponent } from './components/edit-identity/edit-identity.component';
|
||||
import { HomeComponent as EditIdentityHomeComponent } from './components/edit-identity/home/home.component';
|
||||
@@ -66,6 +67,10 @@ export const routes: Routes = [
|
||||
path: 'settings',
|
||||
component: SettingsComponent,
|
||||
},
|
||||
{
|
||||
path: 'logs',
|
||||
component: LogsComponent,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -10,7 +10,12 @@
|
||||
<span class="text-muted" style="font-size: 12px">
|
||||
Nothing configured so far.
|
||||
</span>
|
||||
} @for(hostPermissions of hostsPermissions; track hostPermissions) {
|
||||
} @else {
|
||||
<button class="btn btn-danger btn-sm remove-all-btn" (click)="onClickRemoveAllPermissions()">
|
||||
Remove All Permissions
|
||||
</button>
|
||||
}
|
||||
@for(hostPermissions of hostsPermissions; track hostPermissions) {
|
||||
<div class="permissions-card">
|
||||
<span style="margin-bottom: 4px; font-weight: 500">
|
||||
{{ hostPermissions.host }}
|
||||
|
||||
@@ -17,6 +17,10 @@
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.remove-all-btn {
|
||||
margin-bottom: var(--size);
|
||||
}
|
||||
|
||||
.permissions-card {
|
||||
background: var(--background-light);
|
||||
border-radius: 8px;
|
||||
|
||||
@@ -35,6 +35,14 @@ export class PermissionsComponent extends NavComponent implements OnInit {
|
||||
this.#buildHostsPermissions(this.identity?.id);
|
||||
}
|
||||
|
||||
async onClickRemoveAllPermissions() {
|
||||
const allPermissions = this.hostsPermissions.flatMap(hp => hp.permissions);
|
||||
for (const permission of allPermissions) {
|
||||
await this.#storage.deletePermission(permission.id);
|
||||
}
|
||||
this.#buildHostsPermissions(this.identity?.id);
|
||||
}
|
||||
|
||||
#initialize(identityId: string) {
|
||||
this.identity = this.#storage
|
||||
.getBrowserSessionHandler()
|
||||
|
||||
@@ -33,4 +33,8 @@
|
||||
<a class="tab" routerLink="/home/info" routerLinkActive="active" title="Info">
|
||||
<i class="bi bi-info-circle"></i>
|
||||
</a>
|
||||
|
||||
<a class="tab" routerLink="/home/logs" routerLinkActive="active" title="Logs">
|
||||
<span style="font-size: 1.2rem">🪵</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
<div class="logs-header">
|
||||
<span class="logs-title">Logs</span>
|
||||
<button class="btn btn-sm btn-secondary" (click)="onClear()">Clear Log</button>
|
||||
</div>
|
||||
|
||||
<div class="logs-container">
|
||||
@if (logs.length === 0) {
|
||||
<div class="logs-empty">No logs yet</div>
|
||||
}
|
||||
@for (log of logs; track log.timestamp) {
|
||||
<div class="log-entry" [class]="getLevelClass(log.level)">
|
||||
<span class="log-time">{{ log.timestamp | date:'HH:mm:ss' }}</span>
|
||||
<span class="log-level">{{ log.level }}</span>
|
||||
<span class="log-message">{{ log.message }}</span>
|
||||
@if (log.data) {
|
||||
<pre class="log-data">{{ log.data | json }}</pre>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@@ -0,0 +1,93 @@
|
||||
:host {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: var(--size);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.logs-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: var(--size);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.logs-title {
|
||||
font-weight: 600;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.logs-container {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
background: var(--background-light);
|
||||
border-radius: 8px;
|
||||
padding: var(--size-h);
|
||||
}
|
||||
|
||||
.logs-empty {
|
||||
color: var(--muted-foreground);
|
||||
text-align: center;
|
||||
padding: var(--size);
|
||||
}
|
||||
|
||||
.log-entry {
|
||||
font-family: monospace;
|
||||
font-size: 11px;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 2px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
align-items: flex-start;
|
||||
|
||||
&.log-error {
|
||||
background: rgba(220, 53, 69, 0.15);
|
||||
color: #ff6b6b;
|
||||
}
|
||||
|
||||
&.log-warn {
|
||||
background: rgba(255, 193, 7, 0.15);
|
||||
color: #ffc107;
|
||||
}
|
||||
|
||||
&.log-debug {
|
||||
background: rgba(108, 117, 125, 0.15);
|
||||
color: #adb5bd;
|
||||
}
|
||||
|
||||
&.log-info {
|
||||
background: rgba(13, 110, 253, 0.1);
|
||||
color: var(--foreground);
|
||||
}
|
||||
}
|
||||
|
||||
.log-time {
|
||||
color: var(--muted-foreground);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.log-level {
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
width: 40px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.log-message {
|
||||
flex: 1;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.log-data {
|
||||
width: 100%;
|
||||
margin: 4px 0 0 0;
|
||||
padding: 4px;
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
border-radius: 4px;
|
||||
font-size: 10px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { Component, inject } from '@angular/core';
|
||||
import { LoggerService, LogEntry } from '@common';
|
||||
import { DatePipe, JsonPipe } from '@angular/common';
|
||||
|
||||
@Component({
|
||||
selector: 'app-logs',
|
||||
templateUrl: './logs.component.html',
|
||||
styleUrl: './logs.component.scss',
|
||||
imports: [DatePipe, JsonPipe],
|
||||
})
|
||||
export class LogsComponent {
|
||||
readonly #logger = inject(LoggerService);
|
||||
|
||||
get logs(): LogEntry[] {
|
||||
return this.#logger.logs;
|
||||
}
|
||||
|
||||
onClear() {
|
||||
this.#logger.clear();
|
||||
}
|
||||
|
||||
getLevelClass(level: LogEntry['level']): string {
|
||||
switch (level) {
|
||||
case 'error':
|
||||
return 'log-error';
|
||||
case 'warn':
|
||||
return 'log-warn';
|
||||
case 'debug':
|
||||
return 'log-debug';
|
||||
default:
|
||||
return 'log-info';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
import { Event, EventTemplate, finalizeEvent, nip04, nip44 } from 'nostr-tools';
|
||||
import { FirefoxMetaHandler } from './app/common/data/firefox-meta-handler';
|
||||
import browser from 'webextension-polyfill';
|
||||
import { Buffer } from 'buffer';
|
||||
|
||||
export const debug = function (message: any) {
|
||||
const dateString = new Date().toISOString();
|
||||
@@ -67,6 +68,8 @@ export const shouldRecklessModeApprove = async function (
|
||||
host: string
|
||||
): Promise<boolean> {
|
||||
const signerMetaData = await getSignerMetaData();
|
||||
debug(`shouldRecklessModeApprove: recklessMode=${signerMetaData.recklessMode}, host=${host}`);
|
||||
debug(`Full signerMetaData: ${JSON.stringify(signerMetaData)}`);
|
||||
|
||||
if (!signerMetaData.recklessMode) {
|
||||
return false;
|
||||
@@ -228,8 +231,7 @@ export const storePermission = async function (
|
||||
// Encrypt permission to store in sync storage (depending on sync flow).
|
||||
const encryptedPermission = await encryptPermission(
|
||||
permission,
|
||||
browserSessionData.iv,
|
||||
browserSessionData.vaultPassword as string
|
||||
browserSessionData
|
||||
);
|
||||
|
||||
await savePermissionsToBrowserSyncStorage([
|
||||
@@ -326,22 +328,20 @@ export const nip44Decrypt = async function (
|
||||
|
||||
const encryptPermission = async function (
|
||||
permission: Permission_DECRYPTED,
|
||||
iv: string,
|
||||
password: string
|
||||
sessionData: BrowserSessionData
|
||||
): Promise<Permission_ENCRYPTED> {
|
||||
const encryptedPermission: Permission_ENCRYPTED = {
|
||||
id: await encrypt(permission.id, iv, password),
|
||||
identityId: await encrypt(permission.identityId, iv, password),
|
||||
host: await encrypt(permission.host, iv, password),
|
||||
method: await encrypt(permission.method, iv, password),
|
||||
methodPolicy: await encrypt(permission.methodPolicy, iv, password),
|
||||
id: await encrypt(permission.id, sessionData),
|
||||
identityId: await encrypt(permission.identityId, sessionData),
|
||||
host: await encrypt(permission.host, sessionData),
|
||||
method: await encrypt(permission.method, sessionData),
|
||||
methodPolicy: await encrypt(permission.methodPolicy, sessionData),
|
||||
};
|
||||
|
||||
if (typeof permission.kind !== 'undefined') {
|
||||
encryptedPermission.kind = await encrypt(
|
||||
permission.kind.toString(),
|
||||
iv,
|
||||
password
|
||||
sessionData
|
||||
);
|
||||
}
|
||||
|
||||
@@ -350,8 +350,30 @@ const encryptPermission = async function (
|
||||
|
||||
const encrypt = async function (
|
||||
value: string,
|
||||
iv: string,
|
||||
password: string
|
||||
sessionData: BrowserSessionData
|
||||
): Promise<string> {
|
||||
return await CryptoHelper.encrypt(value, iv, password);
|
||||
// v2: Use pre-derived key with AES-GCM directly
|
||||
if (sessionData.vaultKey) {
|
||||
const keyBytes = Buffer.from(sessionData.vaultKey, 'base64');
|
||||
const iv = Buffer.from(sessionData.iv, 'base64');
|
||||
|
||||
const key = await crypto.subtle.importKey(
|
||||
'raw',
|
||||
keyBytes,
|
||||
{ name: 'AES-GCM' },
|
||||
false,
|
||||
['encrypt']
|
||||
);
|
||||
|
||||
const cipherText = await crypto.subtle.encrypt(
|
||||
{ name: 'AES-GCM', iv },
|
||||
key,
|
||||
new TextEncoder().encode(value)
|
||||
);
|
||||
|
||||
return Buffer.from(cipherText).toString('base64');
|
||||
}
|
||||
|
||||
// v1: Use password with PBKDF2
|
||||
return await CryptoHelper.encrypt(value, sessionData.iv, sessionData.vaultPassword!);
|
||||
};
|
||||
|
||||
@@ -67,6 +67,7 @@ browser.runtime.onMessage.addListener(async (message /*, sender*/) => {
|
||||
|
||||
// Check reckless mode first
|
||||
const recklessApprove = await shouldRecklessModeApprove(req.host);
|
||||
debug(`recklessApprove result: ${recklessApprove}`);
|
||||
if (recklessApprove) {
|
||||
debug('Request auto-approved via reckless mode.');
|
||||
} else {
|
||||
@@ -78,6 +79,7 @@ browser.runtime.onMessage.addListener(async (message /*, sender*/) => {
|
||||
req.method,
|
||||
req.params
|
||||
);
|
||||
debug(`permissionState result: ${permissionState}`);
|
||||
|
||||
if (permissionState === false) {
|
||||
throw new Error('Permission denied');
|
||||
|
||||
@@ -1,14 +1,32 @@
|
||||
import browser from 'webextension-polyfill';
|
||||
import { Buffer } from 'buffer';
|
||||
import { Nip07Method } from '@common';
|
||||
import { PromptResponse, PromptResponseMessage } from './background-common';
|
||||
|
||||
/**
|
||||
* Decode base64 string to UTF-8 using native browser APIs.
|
||||
* This avoids race conditions with the Buffer polyfill initialization.
|
||||
*/
|
||||
function base64ToUtf8(base64: string): string {
|
||||
const binaryString = atob(base64);
|
||||
const bytes = Uint8Array.from(binaryString, char => char.charCodeAt(0));
|
||||
return new TextDecoder('utf-8').decode(bytes);
|
||||
}
|
||||
|
||||
const params = new URLSearchParams(location.search);
|
||||
const id = params.get('id') as string;
|
||||
const method = params.get('method') as Nip07Method;
|
||||
const host = params.get('host') as string;
|
||||
const nick = params.get('nick') as string;
|
||||
const event = Buffer.from(params.get('event') as string, 'base64').toString();
|
||||
|
||||
let event = '{}';
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
let eventParsed: any = {};
|
||||
try {
|
||||
event = base64ToUtf8(params.get('event') as string);
|
||||
eventParsed = JSON.parse(event);
|
||||
} catch (e) {
|
||||
console.error('Failed to parse event:', e);
|
||||
}
|
||||
|
||||
let title = '';
|
||||
switch (method) {
|
||||
@@ -62,8 +80,8 @@ Array.from(document.getElementsByClassName('host-INSERT')).forEach(
|
||||
);
|
||||
|
||||
const kindSpanElement = document.getElementById('kindSpan');
|
||||
if (kindSpanElement) {
|
||||
kindSpanElement.innerText = JSON.parse(event).kind;
|
||||
if (kindSpanElement && eventParsed.kind !== undefined) {
|
||||
kindSpanElement.innerText = eventParsed.kind;
|
||||
}
|
||||
|
||||
const cardGetPublicKeyElement = document.getElementById('cardGetPublicKey');
|
||||
@@ -108,9 +126,8 @@ if (cardNip04EncryptElement && card2Nip04EncryptElement) {
|
||||
'card2Nip04Encrypt_text'
|
||||
);
|
||||
if (card2Nip04Encrypt_textElement) {
|
||||
const eventObject: { peerPubkey: string; plaintext: string } =
|
||||
JSON.parse(event);
|
||||
card2Nip04Encrypt_textElement.innerText = eventObject.plaintext;
|
||||
const eventObject = eventParsed as { peerPubkey: string; plaintext: string };
|
||||
card2Nip04Encrypt_textElement.innerText = eventObject.plaintext || '';
|
||||
}
|
||||
} else {
|
||||
cardNip04EncryptElement.style.display = 'none';
|
||||
@@ -126,9 +143,8 @@ if (cardNip44EncryptElement && card2Nip44EncryptElement) {
|
||||
'card2Nip44Encrypt_text'
|
||||
);
|
||||
if (card2Nip44Encrypt_textElement) {
|
||||
const eventObject: { peerPubkey: string; plaintext: string } =
|
||||
JSON.parse(event);
|
||||
card2Nip44Encrypt_textElement.innerText = eventObject.plaintext;
|
||||
const eventObject = eventParsed as { peerPubkey: string; plaintext: string };
|
||||
card2Nip44Encrypt_textElement.innerText = eventObject.plaintext || '';
|
||||
}
|
||||
} else {
|
||||
cardNip44EncryptElement.style.display = 'none';
|
||||
@@ -144,9 +160,8 @@ if (cardNip04DecryptElement && card2Nip04DecryptElement) {
|
||||
'card2Nip04Decrypt_text'
|
||||
);
|
||||
if (card2Nip04Decrypt_textElement) {
|
||||
const eventObject: { peerPubkey: string; ciphertext: string } =
|
||||
JSON.parse(event);
|
||||
card2Nip04Decrypt_textElement.innerText = eventObject.ciphertext;
|
||||
const eventObject = eventParsed as { peerPubkey: string; ciphertext: string };
|
||||
card2Nip04Decrypt_textElement.innerText = eventObject.ciphertext || '';
|
||||
}
|
||||
} else {
|
||||
cardNip04DecryptElement.style.display = 'none';
|
||||
@@ -162,9 +177,8 @@ if (cardNip44DecryptElement && card2Nip44DecryptElement) {
|
||||
'card2Nip44Decrypt_text'
|
||||
);
|
||||
if (card2Nip44Decrypt_textElement) {
|
||||
const eventObject: { peerPubkey: string; ciphertext: string } =
|
||||
JSON.parse(event);
|
||||
card2Nip44Decrypt_textElement.innerText = eventObject.ciphertext;
|
||||
const eventObject = eventParsed as { peerPubkey: string; ciphertext: string };
|
||||
card2Nip44Decrypt_textElement.innerText = eventObject.ciphertext || '';
|
||||
}
|
||||
} else {
|
||||
cardNip44DecryptElement.style.display = 'none';
|
||||
@@ -176,36 +190,38 @@ if (cardNip44DecryptElement && card2Nip44DecryptElement) {
|
||||
// Functions
|
||||
//
|
||||
|
||||
function deliver(response: PromptResponse) {
|
||||
async function deliver(response: PromptResponse) {
|
||||
const message: PromptResponseMessage = {
|
||||
id,
|
||||
response,
|
||||
};
|
||||
|
||||
browser.runtime.sendMessage(message);
|
||||
try {
|
||||
await browser.runtime.sendMessage(message);
|
||||
} catch (error) {
|
||||
console.error('Failed to send message:', error);
|
||||
}
|
||||
window.close();
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const rejectJustOnceButton = document.getElementById('rejectJustOnceButton');
|
||||
rejectJustOnceButton?.addEventListener('click', () => {
|
||||
const rejectOnceButton = document.getElementById('rejectOnceButton');
|
||||
rejectOnceButton?.addEventListener('click', () => {
|
||||
deliver('reject-once');
|
||||
});
|
||||
|
||||
const rejectButton = document.getElementById('rejectButton');
|
||||
rejectButton?.addEventListener('click', () => {
|
||||
const rejectAlwaysButton = document.getElementById('rejectAlwaysButton');
|
||||
rejectAlwaysButton?.addEventListener('click', () => {
|
||||
deliver('reject');
|
||||
});
|
||||
|
||||
const approveJustOnceButton = document.getElementById(
|
||||
'approveJustOnceButton'
|
||||
);
|
||||
approveJustOnceButton?.addEventListener('click', () => {
|
||||
const approveOnceButton = document.getElementById('approveOnceButton');
|
||||
approveOnceButton?.addEventListener('click', () => {
|
||||
deliver('approve-once');
|
||||
});
|
||||
|
||||
const approveButton = document.getElementById('approveButton');
|
||||
approveButton?.addEventListener('click', () => {
|
||||
const approveAlwaysButton = document.getElementById('approveAlwaysButton');
|
||||
approveAlwaysButton?.addEventListener('click', () => {
|
||||
deliver('approve');
|
||||
});
|
||||
});
|
||||
|
||||