Release v1.0.11 - Add dev mode with test prompt button on all headers

- Add Dev Mode toggle to settings that persists in vault metadata
- Add test permission prompt button () to all page headers when dev mode enabled
- Move devMode and onTestPrompt to NavComponent base class for inheritance
- Refactor all home components to extend NavComponent
- Simplify permission prompt layout: remove duplicate domain from header
- Convert permission descriptions to flowing single paragraphs
- Update header-buttons styling for consistent lock/magic button layout

Files modified:
- projects/common/src/lib/common/nav-component.ts (devMode, onTestPrompt)
- projects/common/src/lib/services/storage/types.ts (devMode property)
- projects/common/src/lib/services/storage/signer-meta-handler.ts (setDevMode)
- projects/common/src/lib/styles/_common.scss (header-buttons styling)
- projects/*/src/app/components/home/*/settings.component.* (dev mode UI)
- projects/*/src/app/components/home/*/*.component.* (extend NavComponent)
- projects/*/public/prompt.html (simplified layout)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
woikos
2025-12-23 07:41:51 +01:00
parent 5ca6eb177c
commit 586e2ab23f
49 changed files with 745 additions and 470 deletions

View File

@@ -1,12 +1,12 @@
{ {
"name": "plebeian-signer", "name": "plebeian-signer",
"version": "v1.0.10", "version": "v1.0.11",
"custom": { "custom": {
"chrome": { "chrome": {
"version": "v1.0.10" "version": "v1.0.11"
}, },
"firefox": { "firefox": {
"version": "v1.0.10" "version": "v1.0.11"
} }
}, },
"scripts": { "scripts": {

View File

@@ -2,7 +2,7 @@
"manifest_version": 3, "manifest_version": 3,
"name": "Plebeian Signer - Nostr Identity Manager & Signer", "name": "Plebeian Signer - Nostr Identity Manager & Signer",
"description": "Manage and switch between multiple identities while interacting with Nostr apps", "description": "Manage and switch between multiple identities while interacting with Nostr apps",
"version": "1.0.10", "version": "1.0.11",
"homepage_url": "https://github.com/PlebeianApp/plebeian-signer", "homepage_url": "https://github.com/PlebeianApp/plebeian-signer",
"options_page": "options.html", "options_page": "options.html",
"permissions": [ "permissions": [

View File

@@ -27,11 +27,66 @@
.page { .page {
height: 100%; height: 100%;
display: grid; display: grid;
grid-template-rows: 1fr 60px; grid-template-rows: 1fr auto;
grid-template-columns: 1fr; grid-template-columns: 1fr;
overflow-y: hidden; overflow-y: hidden;
} }
.actions {
display: flex;
flex-direction: column;
gap: 8px;
padding: var(--size);
background: var(--background);
}
.action-row {
display: flex;
align-items: center;
gap: 8px;
}
.action-label {
width: 60px;
font-size: 13px;
font-weight: 500;
color: var(--muted-foreground);
}
.action-buttons {
display: flex;
gap: 8px;
flex: 1;
}
.action-buttons button {
flex: 1;
padding: 8px 12px;
border-radius: 6px;
font-size: 13px;
font-weight: 500;
cursor: pointer;
border: none;
}
.btn-reject {
background: var(--muted);
color: var(--foreground);
}
.btn-reject:hover {
background: var(--border);
}
.btn-accept {
background: var(--primary);
color: var(--primary-foreground);
}
.btn-accept:hover {
opacity: 0.9;
}
.card { .card {
padding: var(--size); padding: var(--size);
background: var(--background-light); background: var(--background-light);
@@ -54,6 +109,12 @@
font-size: 12px; font-size: 12px;
color: gray; color: gray;
} }
.description {
margin: 0;
text-align: center;
line-height: 1.5;
}
</style> </style>
</head> </head>
<body> <body>
@@ -63,64 +124,31 @@
<span id="titleSpan" style="font-weight: 400 !important"></span> <span id="titleSpan" style="font-weight: 400 !important"></span>
</div> </div>
<span
class="host-INSERT sam-align-self-center sam-text-muted"
style="font-weight: 500"
></span>
<!-- Card for getPublicKey --> <!-- Card for getPublicKey -->
<div id="cardGetPublicKey" class="card sam-mt sam-ml sam-mr"> <div id="cardGetPublicKey" class="card sam-mt sam-ml sam-mr">
<span style="text-align: center"> <p class="description">
<b><span class="host-INSERT color-primary"></span></b> <b class="host-INSERT color-primary"></b> is requesting permission to
is requesting permission to<br /> <b class="color-primary">read your public key</b> for the selected identity
<br /> <b class="nick-INSERT color-primary"></b>.
<b class="color-primary">read your public key</b> <br /> </p>
<br />
<span>
for the selected identity
<span
style="font-weight: 500"
class="nick-INSERT color-primary"
></span>
</span>
</span>
</div> </div>
<!-- Card for getRelays --> <!-- Card for getRelays -->
<div id="cardGetRelays" class="card sam-mt sam-ml sam-mr"> <div id="cardGetRelays" class="card sam-mt sam-ml sam-mr">
<span style="text-align: center"> <p class="description">
<b><span class="host-INSERT color-primary"></span></b> <b class="host-INSERT color-primary"></b> is requesting permission to
is requesting permission to<br /> <b class="color-primary">read your relays</b> for the selected identity
<br /> <b class="nick-INSERT color-primary"></b>.
<b class="color-primary">read your relays</b> <br /> </p>
<br />
<span>
for the selected identity
<span
style="font-weight: 500"
class="nick-INSERT color-primary"
></span>
</span>
</span>
</div> </div>
<!-- Card for signEvent --> <!-- Card for signEvent -->
<div id="cardSignEvent" class="card sam-mt sam-ml sam-mr"> <div id="cardSignEvent" class="card sam-mt sam-ml sam-mr">
<span style="text-align: center"> <p class="description">
<b><span class="host-INSERT color-primary"></span></b> <b class="host-INSERT color-primary"></b> is requesting permission to
is requesting permission to<br /> <b class="color-primary">sign an event</b> (kind <span id="kindSpan"></span>)
<br /> for the selected identity <b class="nick-INSERT color-primary"></b>.
<b class="color-primary">sign an event</b> (kind </p>
<span id="kindSpan"></span>) <br />
<br />
<span>
for the selected identity
<span
style="font-weight: 500"
class="nick-INSERT color-primary"
></span>
</span>
</span>
</div> </div>
<!-- Card2 for signEvent --> <!-- Card2 for signEvent -->
@@ -130,20 +158,11 @@
<!-- Card for nip04.encrypt --> <!-- Card for nip04.encrypt -->
<div id="cardNip04Encrypt" class="card sam-mt sam-ml sam-mr"> <div id="cardNip04Encrypt" class="card sam-mt sam-ml sam-mr">
<span style="text-align: center"> <p class="description">
<b><span class="host-INSERT color-primary"></span></b> <b class="host-INSERT color-primary"></b> is requesting permission to
is requesting permission to<br /> <b class="color-primary">encrypt a text</b> (NIP04) for the selected identity
<br /> <b class="nick-INSERT color-primary"></b>.
<b class="color-primary">encrypt a text</b> (NIP04) <br /> </p>
<br />
<span>
for the selected identity
<span
style="font-weight: 500"
class="nick-INSERT color-primary"
></span>
</span>
</span>
</div> </div>
<!-- Card2 for nip04.encrypt --> <!-- Card2 for nip04.encrypt -->
@@ -153,20 +172,11 @@
<!-- Card for nip44.encrypt --> <!-- Card for nip44.encrypt -->
<div id="cardNip44Encrypt" class="card sam-mt sam-ml sam-mr"> <div id="cardNip44Encrypt" class="card sam-mt sam-ml sam-mr">
<span style="text-align: center"> <p class="description">
<b><span class="host-INSERT color-primary"></span></b> <b class="host-INSERT color-primary"></b> is requesting permission to
is requesting permission to<br /> <b class="color-primary">encrypt a text</b> (NIP44) for the selected identity
<br /> <b class="nick-INSERT color-primary"></b>.
<b class="color-primary">encrypt a text</b> (NIP44) <br /> </p>
<br />
<span>
for the selected identity
<span
style="font-weight: 500"
class="nick-INSERT color-primary"
></span>
</span>
</span>
</div> </div>
<!-- Card2 for nip44.encrypt --> <!-- Card2 for nip44.encrypt -->
@@ -176,20 +186,11 @@
<!-- Card for nip04.decrypt --> <!-- Card for nip04.decrypt -->
<div id="cardNip04Decrypt" class="card sam-mt sam-ml sam-mr"> <div id="cardNip04Decrypt" class="card sam-mt sam-ml sam-mr">
<span style="text-align: center"> <p class="description">
<b><span class="host-INSERT color-primary"></span></b> <b class="host-INSERT color-primary"></b> is requesting permission to
is requesting permission to<br /> <b class="color-primary">decrypt a text</b> (NIP04) for the selected identity
<br /> <b class="nick-INSERT color-primary"></b>.
<b class="color-primary">decrypt a text</b> (NIP04) <br /> </p>
<br />
<span>
for the selected identity
<span
style="font-weight: 500"
class="nick-INSERT color-primary"
></span>
</span>
</span>
</div> </div>
<!-- Card2 for nip04.decrypt --> <!-- Card2 for nip04.decrypt -->
@@ -199,20 +200,11 @@
<!-- Card for nip44.decrypt --> <!-- Card for nip44.decrypt -->
<div id="cardNip44Decrypt" class="card sam-mt sam-ml sam-mr"> <div id="cardNip44Decrypt" class="card sam-mt sam-ml sam-mr">
<span style="text-align: center"> <p class="description">
<b><span class="host-INSERT color-primary"></span></b> <b class="host-INSERT color-primary"></b> is requesting permission to
is requesting permission to<br /> <b class="color-primary">decrypt a text</b> (NIP44) for the selected identity
<br /> <b class="nick-INSERT color-primary"></b>.
<b class="color-primary">decrypt a text</b> (NIP44) <br /> </p>
<br />
<span>
for the selected identity
<span
style="font-weight: 500"
class="nick-INSERT color-primary"
></span>
</span>
</span>
</div> </div>
<!-- Card2 for nip44.decrypt --> <!-- Card2 for nip44.decrypt -->
@@ -224,47 +216,20 @@
<!-------------> <!------------->
<!-- ACTIONS --> <!-- ACTIONS -->
<!-------------> <!------------->
<div class="sam-footer-grid-2"> <div class="actions">
<div class="btn-group"> <div class="action-row">
<button id="rejectOnceButton" type="button" class="btn btn-secondary"> <span class="action-label">Reject</span>
Reject <div class="action-buttons">
</button> <button id="rejectOnceButton" type="button" class="btn-reject">Once</button>
<button <button id="rejectAlwaysButton" type="button" class="btn-reject">Always</button>
type="button" </div>
class="btn btn-secondary dropdown-toggle dropdown-toggle-split"
data-bs-toggle="dropdown"
aria-expanded="false"
>
<span class="visually-hidden">Toggle Dropdown</span>
</button>
<ul class="dropdown-menu dropdown-menu-end">
<li>
<button id="rejectAlwaysButton" class="dropdown-item">
Reject Always
</button>
</li>
</ul>
</div> </div>
<div class="action-row">
<div class="btn-group"> <span class="action-label">Accept</span>
<button id="approveAlwaysButton" type="button" class="btn btn-primary"> <div class="action-buttons">
Approve Always <button id="approveOnceButton" type="button" class="btn-accept">Once</button>
</button> <button id="approveAlwaysButton" type="button" class="btn-accept">Always</button>
<button </div>
type="button"
class="btn btn-primary dropdown-toggle dropdown-toggle-split"
data-bs-toggle="dropdown"
aria-expanded="false"
>
<span class="visually-hidden">Toggle Dropdown</span>
</button>
<ul class="dropdown-menu">
<li>
<button id="approveOnceButton" class="dropdown-item">
Approve Once
</button>
</li>
</ul>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,7 +1,14 @@
<div class="sam-text-header"> <div class="sam-text-header">
<button class="lock-btn" title="Lock" (click)="onClickLock()"> <div class="header-buttons">
<span class="emoji">🔒</span> <button class="header-btn" title="Lock" (click)="onClickLock()">
</button> <span class="emoji">🔒</span>
</button>
@if (devMode) {
<button class="header-btn" title="Test Permission Prompt" (click)="onTestPrompt()">
<span class="emoji"></span>
</button>
}
</div>
<button class="back-btn" title="Go Back" (click)="goBack()"> <button class="back-btn" title="Go Back" (click)="goBack()">
<span class="emoji"></span> <span class="emoji"></span>
</button> </button>

View File

@@ -3,9 +3,9 @@ import { Router } from '@angular/router';
import { import {
ConfirmComponent, ConfirmComponent,
LoggerService, LoggerService,
NavComponent,
SignerMetaData_VaultSnapshot, SignerMetaData_VaultSnapshot,
StartupService, StartupService,
StorageService,
} from '@common'; } from '@common';
import { getNewStorageServiceConfig } from '../../../common/data/get-new-storage-service-config'; import { getNewStorageServiceConfig } from '../../../common/data/get-new-storage-service-config';
@@ -15,9 +15,8 @@ import { getNewStorageServiceConfig } from '../../../common/data/get-new-storage
styleUrl: './backups.component.scss', styleUrl: './backups.component.scss',
imports: [ConfirmComponent], imports: [ConfirmComponent],
}) })
export class BackupsComponent implements OnInit { export class BackupsComponent extends NavComponent implements OnInit {
readonly #router = inject(Router); readonly #router = inject(Router);
readonly #storage = inject(StorageService);
readonly #startup = inject(StartupService); readonly #startup = inject(StartupService);
readonly #logger = inject(LoggerService); readonly #logger = inject(LoggerService);
@@ -27,11 +26,11 @@ export class BackupsComponent implements OnInit {
ngOnInit(): void { ngOnInit(): void {
this.loadBackups(); this.loadBackups();
this.maxBackups = this.#storage.getSignerMetaHandler().getMaxBackups(); this.maxBackups = this.storage.getSignerMetaHandler().getMaxBackups();
} }
loadBackups(): void { loadBackups(): void {
this.backups = this.#storage.getSignerMetaHandler().getBackups(); this.backups = this.storage.getSignerMetaHandler().getBackups();
} }
async onMaxBackupsChange(event: Event): Promise<void> { async onMaxBackupsChange(event: Event): Promise<void> {
@@ -39,14 +38,14 @@ export class BackupsComponent implements OnInit {
const value = parseInt(input.value, 10); const value = parseInt(input.value, 10);
if (!isNaN(value) && value >= 1 && value <= 20) { if (!isNaN(value) && value >= 1 && value <= 20) {
this.maxBackups = value; this.maxBackups = value;
await this.#storage.getSignerMetaHandler().setMaxBackups(value); await this.storage.getSignerMetaHandler().setMaxBackups(value);
} }
} }
async createManualBackup(): Promise<void> { async createManualBackup(): Promise<void> {
const browserSyncData = this.#storage.getBrowserSyncHandler().browserSyncData; const browserSyncData = this.storage.getBrowserSyncHandler().browserSyncData;
if (browserSyncData) { if (browserSyncData) {
await this.#storage.getSignerMetaHandler().createBackup(browserSyncData, 'manual'); await this.storage.getSignerMetaHandler().createBackup(browserSyncData, 'manual');
this.loadBackups(); this.loadBackups();
} }
} }
@@ -55,22 +54,22 @@ export class BackupsComponent implements OnInit {
this.restoringBackupId = backupId; this.restoringBackupId = backupId;
try { try {
// First, create a pre-restore backup of current state // First, create a pre-restore backup of current state
const currentData = this.#storage.getBrowserSyncHandler().browserSyncData; const currentData = this.storage.getBrowserSyncHandler().browserSyncData;
if (currentData) { if (currentData) {
await this.#storage.getSignerMetaHandler().createBackup(currentData, 'pre-restore'); await this.storage.getSignerMetaHandler().createBackup(currentData, 'pre-restore');
} }
// Get the backup data // Get the backup data
const backupData = this.#storage.getSignerMetaHandler().getBackupData(backupId); const backupData = this.storage.getSignerMetaHandler().getBackupData(backupId);
if (!backupData) { if (!backupData) {
throw new Error('Backup not found'); throw new Error('Backup not found');
} }
// Import the backup // Import the backup
await this.#storage.deleteVault(true); await this.storage.deleteVault(true);
await this.#storage.importVault(backupData); await this.storage.importVault(backupData);
this.#logger.logVaultImport('Backup Restore'); this.#logger.logVaultImport('Backup Restore');
this.#storage.isInitialized = false; this.storage.isInitialized = false;
this.#startup.startOver(getNewStorageServiceConfig()); this.#startup.startOver(getNewStorageServiceConfig());
} catch (error) { } catch (error) {
console.error('Failed to restore backup:', error); console.error('Failed to restore backup:', error);
@@ -79,7 +78,7 @@ export class BackupsComponent implements OnInit {
} }
async deleteBackup(backupId: string): Promise<void> { async deleteBackup(backupId: string): Promise<void> {
await this.#storage.getSignerMetaHandler().deleteBackup(backupId); await this.storage.getSignerMetaHandler().deleteBackup(backupId);
this.loadBackups(); this.loadBackups();
} }
@@ -120,7 +119,7 @@ export class BackupsComponent implements OnInit {
async onClickLock(): Promise<void> { async onClickLock(): Promise<void> {
this.#logger.logVaultLock(); this.#logger.logVaultLock();
await this.#storage.lockVault(); await this.storage.lockVault();
this.#router.navigateByUrl('/vault-login'); this.#router.navigateByUrl('/vault-login');
} }
} }

View File

@@ -1,9 +1,16 @@
<!-- eslint-disable @angular-eslint/template/interactive-supports-focus --> <!-- eslint-disable @angular-eslint/template/interactive-supports-focus -->
<!-- eslint-disable @angular-eslint/template/click-events-have-key-events --> <!-- eslint-disable @angular-eslint/template/click-events-have-key-events -->
<div class="sam-text-header"> <div class="sam-text-header">
<button class="lock-btn" title="Lock" (click)="onClickLock()"> <div class="header-buttons">
<span class="emoji">🔒</span> <button class="header-btn" title="Lock" (click)="onClickLock()">
</button> <span class="emoji">🔒</span>
</button>
@if (devMode) {
<button class="header-btn" title="Test Permission Prompt" (click)="onTestPrompt()">
<span class="emoji"></span>
</button>
}
</div>
<span>Bookmarks</span> <span>Bookmarks</span>
<button class="add-btn" title="Bookmark This Page" (click)="onBookmarkThisPage()"> <button class="add-btn" title="Bookmark This Page" (click)="onBookmarkThisPage()">
<span class="emoji"></span> <span class="emoji"></span>

View File

@@ -1,6 +1,6 @@
import { Component, inject, OnInit } from '@angular/core'; import { Component, inject, OnInit } from '@angular/core';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { Bookmark, LoggerService, SignerMetaData, StorageService } from '@common'; import { Bookmark, LoggerService, NavComponent, SignerMetaData } from '@common';
import { ChromeMetaHandler } from '../../../common/data/chrome-meta-handler'; import { ChromeMetaHandler } from '../../../common/data/chrome-meta-handler';
@Component({ @Component({
@@ -9,10 +9,9 @@ import { ChromeMetaHandler } from '../../../common/data/chrome-meta-handler';
styleUrl: './bookmarks.component.scss', styleUrl: './bookmarks.component.scss',
imports: [], imports: [],
}) })
export class BookmarksComponent implements OnInit { export class BookmarksComponent extends NavComponent implements OnInit {
readonly #logger = inject(LoggerService); readonly #logger = inject(LoggerService);
readonly #metaHandler = new ChromeMetaHandler(); readonly #metaHandler = new ChromeMetaHandler();
readonly #storage = inject(StorageService);
readonly #router = inject(Router); readonly #router = inject(Router);
bookmarks: Bookmark[] = []; bookmarks: Bookmark[] = [];
@@ -93,7 +92,7 @@ export class BookmarksComponent implements OnInit {
async onClickLock() { async onClickLock() {
this.#logger.logVaultLock(); this.#logger.logVaultLock();
await this.#storage.lockVault(); await this.storage.lockVault();
this.#router.navigateByUrl('/vault-login'); this.#router.navigateByUrl('/vault-login');
} }
} }

View File

@@ -1,9 +1,16 @@
<!-- eslint-disable @angular-eslint/template/interactive-supports-focus --> <!-- eslint-disable @angular-eslint/template/interactive-supports-focus -->
<!-- eslint-disable @angular-eslint/template/click-events-have-key-events --> <!-- eslint-disable @angular-eslint/template/click-events-have-key-events -->
<div class="custom-header" style="position: sticky; top: 0"> <div class="custom-header" style="position: sticky; top: 0">
<button class="lock-btn" title="Lock" (click)="onClickLock()"> <div class="header-buttons">
<span class="emoji">🔒</span> <button class="header-btn" title="Lock" (click)="onClickLock()">
</button> <span class="emoji">🔒</span>
</button>
@if (devMode) {
<button class="header-btn" title="Test Permission Prompt" (click)="onTestPrompt()">
<span class="emoji"></span>
</button>
}
</div>
<span class="text">Identities</span> <span class="text">Identities</span>
<button class="add-btn" title="New Identity" (click)="onClickNewIdentity()"> <button class="add-btn" title="New Identity" (click)="onClickNewIdentity()">

View File

@@ -19,9 +19,16 @@
background: var(--background); background: var(--background);
position: relative; position: relative;
.lock-btn, .header-buttons {
.add-btn {
position: absolute; position: absolute;
left: 0;
display: flex;
flex-direction: row;
align-items: center;
}
.header-btn,
.add-btn {
background: transparent; background: transparent;
border: none; border: none;
padding: 8px; padding: 8px;
@@ -41,11 +48,8 @@
} }
} }
.lock-btn {
left: 0;
}
.add-btn { .add-btn {
position: absolute;
right: 0; right: 0;
} }

View File

@@ -4,6 +4,7 @@ import {
IconButtonComponent, IconButtonComponent,
Identity_DECRYPTED, Identity_DECRYPTED,
LoggerService, LoggerService,
NavComponent,
NostrHelper, NostrHelper,
ProfileMetadata, ProfileMetadata,
ProfileMetadataService, ProfileMetadataService,
@@ -17,8 +18,8 @@ import {
styleUrl: './identities.component.scss', styleUrl: './identities.component.scss',
imports: [IconButtonComponent, ToastComponent], imports: [IconButtonComponent, ToastComponent],
}) })
export class IdentitiesComponent implements OnInit { export class IdentitiesComponent extends NavComponent implements OnInit {
readonly storage = inject(StorageService); override readonly storage = inject(StorageService);
readonly #router = inject(Router); readonly #router = inject(Router);
readonly #profileMetadata = inject(ProfileMetadataService); readonly #profileMetadata = inject(ProfileMetadataService);
readonly #logger = inject(LoggerService); readonly #logger = inject(LoggerService);

View File

@@ -1,9 +1,16 @@
<!-- eslint-disable @angular-eslint/template/interactive-supports-focus --> <!-- eslint-disable @angular-eslint/template/interactive-supports-focus -->
<!-- eslint-disable @angular-eslint/template/click-events-have-key-events --> <!-- eslint-disable @angular-eslint/template/click-events-have-key-events -->
<div class="sam-text-header"> <div class="sam-text-header">
<button class="lock-btn" title="Lock" (click)="onClickLock()"> <div class="header-buttons">
<span class="emoji">🔒</span> <button class="header-btn" title="Lock" (click)="onClickLock()">
</button> <span class="emoji">🔒</span>
</button>
@if (devMode) {
<button class="header-btn" title="Test Permission Prompt" (click)="onTestPrompt()">
<span class="emoji"></span>
</button>
}
</div>
<span>You</span> <span>You</span>
<button class="edit-btn" title="Edit profile" (click)="onClickEditProfile()"> <button class="edit-btn" title="Edit profile" (click)="onClickEditProfile()">
<span class="emoji">📝</span> <span class="emoji">📝</span>

View File

@@ -3,11 +3,11 @@ import { Router } from '@angular/router';
import { import {
Identity_DECRYPTED, Identity_DECRYPTED,
LoggerService, LoggerService,
NavComponent,
NostrHelper, NostrHelper,
ProfileMetadata, ProfileMetadata,
ProfileMetadataService, ProfileMetadataService,
PubkeyComponent, PubkeyComponent,
StorageService,
ToastComponent, ToastComponent,
VisualNip05Pipe, VisualNip05Pipe,
validateNip05, validateNip05,
@@ -19,7 +19,7 @@ import {
templateUrl: './identity.component.html', templateUrl: './identity.component.html',
styleUrl: './identity.component.scss', styleUrl: './identity.component.scss',
}) })
export class IdentityComponent implements OnInit { export class IdentityComponent extends NavComponent implements OnInit {
selectedIdentity: Identity_DECRYPTED | undefined; selectedIdentity: Identity_DECRYPTED | undefined;
selectedIdentityNpub: string | undefined; selectedIdentityNpub: string | undefined;
profile: ProfileMetadata | null = null; profile: ProfileMetadata | null = null;
@@ -27,7 +27,6 @@ export class IdentityComponent implements OnInit {
validating = false; validating = false;
loading = true; loading = true;
readonly #storage = inject(StorageService);
readonly #router = inject(Router); readonly #router = inject(Router);
readonly #profileMetadata = inject(ProfileMetadataService); readonly #profileMetadata = inject(ProfileMetadataService);
readonly #logger = inject(LoggerService); readonly #logger = inject(LoggerService);
@@ -82,17 +81,17 @@ export class IdentityComponent implements OnInit {
async onClickLock() { async onClickLock() {
this.#logger.logVaultLock(); this.#logger.logVaultLock();
await this.#storage.lockVault(); await this.storage.lockVault();
this.#router.navigateByUrl('/vault-login'); this.#router.navigateByUrl('/vault-login');
} }
async #loadData() { async #loadData() {
try { try {
const selectedIdentityId = const selectedIdentityId =
this.#storage.getBrowserSessionHandler().browserSessionData this.storage.getBrowserSessionHandler().browserSessionData
?.selectedIdentityId ?? null; ?.selectedIdentityId ?? null;
const identity = this.#storage const identity = this.storage
.getBrowserSessionHandler() .getBrowserSessionHandler()
.browserSessionData?.identities.find( .browserSessionData?.identities.find(
(x) => x.id === selectedIdentityId (x) => x.id === selectedIdentityId

View File

@@ -1,7 +1,14 @@
<div class="sam-text-header"> <div class="sam-text-header">
<button class="lock-btn" title="Lock" (click)="onClickLock()"> <div class="header-buttons">
<span class="emoji">🔒</span> <button class="header-btn" title="Lock" (click)="onClickLock()">
</button> <span class="emoji">🔒</span>
</button>
@if (devMode) {
<button class="header-btn" title="Test Permission Prompt" (click)="onTestPrompt()">
<span class="emoji"></span>
</button>
}
</div>
<span> Plebeian Signer </span> <span> Plebeian Signer </span>
</div> </div>

View File

@@ -1,6 +1,6 @@
import { Component, inject } from '@angular/core'; import { Component, inject } from '@angular/core';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { LoggerService, StorageService } from '@common'; import { LoggerService, NavComponent } from '@common';
import packageJson from '../../../../../../../package.json'; import packageJson from '../../../../../../../package.json';
@Component({ @Component({
@@ -8,16 +8,15 @@ import packageJson from '../../../../../../../package.json';
templateUrl: './info.component.html', templateUrl: './info.component.html',
styleUrl: './info.component.scss', styleUrl: './info.component.scss',
}) })
export class InfoComponent { export class InfoComponent extends NavComponent {
readonly #logger = inject(LoggerService); readonly #logger = inject(LoggerService);
readonly #storage = inject(StorageService);
readonly #router = inject(Router); readonly #router = inject(Router);
version = packageJson.custom.chrome.version; version = packageJson.custom.chrome.version;
async onClickLock() { async onClickLock() {
this.#logger.logVaultLock(); this.#logger.logVaultLock();
await this.#storage.lockVault(); await this.storage.lockVault();
this.#router.navigateByUrl('/vault-login'); this.#router.navigateByUrl('/vault-login');
} }
} }

View File

@@ -1,7 +1,14 @@
<div class="sam-text-header"> <div class="sam-text-header">
<button class="lock-btn" title="Lock" (click)="onClickLock()"> <div class="header-buttons">
<span class="emoji">🔒</span> <button class="header-btn" title="Lock" (click)="onClickLock()">
</button> <span class="emoji">🔒</span>
</button>
@if (devMode) {
<button class="header-btn" title="Test Permission Prompt" (click)="onTestPrompt()">
<span class="emoji"></span>
</button>
}
</div>
<span>Logs</span> <span>Logs</span>
<div class="logs-actions"> <div class="logs-actions">
<button class="btn btn-sm btn-secondary" title="Refresh logs" (click)="onRefresh()">Refresh</button> <button class="btn btn-sm btn-secondary" title="Refresh logs" (click)="onRefresh()">Refresh</button>

View File

@@ -1,6 +1,6 @@
import { Component, inject, OnInit } from '@angular/core'; import { Component, inject, OnInit } from '@angular/core';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { LoggerService, LogEntry, StorageService } from '@common'; import { LoggerService, LogEntry, NavComponent } from '@common';
import { DatePipe } from '@angular/common'; import { DatePipe } from '@angular/common';
@Component({ @Component({
@@ -9,9 +9,8 @@ import { DatePipe } from '@angular/common';
styleUrl: './logs.component.scss', styleUrl: './logs.component.scss',
imports: [DatePipe], imports: [DatePipe],
}) })
export class LogsComponent implements OnInit { export class LogsComponent extends NavComponent implements OnInit {
readonly #logger = inject(LoggerService); readonly #logger = inject(LoggerService);
readonly #storage = inject(StorageService);
readonly #router = inject(Router); readonly #router = inject(Router);
get logs(): LogEntry[] { get logs(): LogEntry[] {
@@ -46,7 +45,7 @@ export class LogsComponent implements OnInit {
async onClickLock() { async onClickLock() {
this.#logger.logVaultLock(); this.#logger.logVaultLock();
await this.#storage.lockVault(); await this.storage.lockVault();
this.#router.navigateByUrl('/vault-login'); this.#router.navigateByUrl('/vault-login');
} }
} }

View File

@@ -1,10 +1,39 @@
<div class="sam-text-header"> <div class="sam-text-header">
<button class="lock-btn" title="Lock" (click)="onClickLock()"> <div class="header-buttons">
<span class="emoji">🔒</span> <button class="header-btn" title="Lock" (click)="onClickLock()">
</button> <span class="emoji">🔒</span>
</button>
@if (devMode) {
<button class="header-btn" title="Test Permission Prompt" (click)="onTestPrompt()">
<span class="emoji"></span>
</button>
}
</div>
<span> Settings </span> <span> Settings </span>
</div> </div>
<div class="vault-buttons">
<button class="btn btn-primary" (click)="onClickExportVault()">
Export Vault
</button>
<button class="btn btn-primary" (click)="navigate('/vault-import')">
Import Vault
</button>
</div>
<lib-nav-item text="💾 Backups" (click)="navigate('/home/backups')"></lib-nav-item>
<lib-nav-item text="🪵 Logs" (click)="navigate('/home/logs')"></lib-nav-item>
<lib-nav-item text="💡 Info" (click)="navigate('/home/info')"></lib-nav-item>
<div class="dev-mode-row">
<label class="toggle-label">
<input type="checkbox" [checked]="devMode" (change)="onToggleDevMode($event)" />
<span>Dev Mode</span>
</label>
</div>
<div class="sam-flex-grow"></div>
<div class="sync-info"> <div class="sync-info">
<span class="sync-label">SYNC: {{ syncFlow }}</span> <span class="sync-label">SYNC: {{ syncFlow }}</span>
<p class="sync-note"> <p class="sync-note">
@@ -13,20 +42,6 @@
</p> </p>
</div> </div>
<button class="btn btn-primary" (click)="onClickExportVault()">
Export Vault
</button>
<button class="btn btn-primary" (click)="navigate('/vault-import')">
Import Vault
</button>
<lib-nav-item text="💾 Backups" (click)="navigate('/home/backups')"></lib-nav-item>
<lib-nav-item text="🪵 Logs" (click)="navigate('/home/logs')"></lib-nav-item>
<lib-nav-item text="💡 Info" (click)="navigate('/home/info')"></lib-nav-item>
<div class="sam-flex-grow"></div>
<button <button
class="btn btn-danger" class="btn btn-danger"
(click)=" (click)="

View File

@@ -16,6 +16,35 @@
} }
} }
.vault-buttons {
display: flex;
gap: var(--size);
button {
flex: 1;
}
}
.dev-mode-row {
display: flex;
align-items: center;
gap: var(--size);
.toggle-label {
display: flex;
align-items: center;
gap: var(--size-h);
cursor: pointer;
font-size: 0.9rem;
input[type="checkbox"] {
width: 16px;
height: 16px;
cursor: pointer;
}
}
}
.sync-info { .sync-info {
.sync-label { .sync-label {
display: block; display: block;

View File

@@ -12,6 +12,7 @@ import {
StorageService, StorageService,
} from '@common'; } from '@common';
import { getNewStorageServiceConfig } from '../../../common/data/get-new-storage-service-config'; import { getNewStorageServiceConfig } from '../../../common/data/get-new-storage-service-config';
import { Buffer } from 'buffer';
@Component({ @Component({
selector: 'app-settings', selector: 'app-settings',
@@ -22,6 +23,7 @@ import { getNewStorageServiceConfig } from '../../../common/data/get-new-storage
export class SettingsComponent extends NavComponent implements OnInit { export class SettingsComponent extends NavComponent implements OnInit {
readonly #router = inject(Router); readonly #router = inject(Router);
syncFlow: string | undefined; syncFlow: string | undefined;
override devMode = false;
readonly #storage = inject(StorageService); readonly #storage = inject(StorageService);
readonly #startup = inject(StartupService); readonly #startup = inject(StartupService);
@@ -45,6 +47,44 @@ export class SettingsComponent extends NavComponent implements OnInit {
default: default:
break; break;
} }
// Load dev mode setting
this.devMode = this.#storage.getSignerMetaHandler().signerMetaData?.devMode ?? false;
}
async onToggleDevMode(event: Event) {
const checked = (event.target as HTMLInputElement).checked;
this.devMode = checked;
await this.#storage.getSignerMetaHandler().setDevMode(checked);
}
override async onTestPrompt() {
// Open a test permission prompt window
const testEvent = {
kind: 1,
content: 'This is a test note for permission prompt preview.',
tags: [],
created_at: Math.floor(Date.now() / 1000),
};
const base64Event = Buffer.from(JSON.stringify(testEvent, null, 2)).toString('base64');
const currentIdentity = this.#storage.getBrowserSessionHandler().browserSessionData?.identities.find(
i => i.id === this.#storage.getBrowserSessionHandler().browserSessionData?.selectedIdentityId
);
const nick = currentIdentity?.nick ?? 'Test Identity';
const width = 375;
const height = 600;
const left = Math.round((screen.width - width) / 2);
const top = Math.round((screen.height - height) / 2);
chrome.windows.create({
type: 'popup',
url: `prompt.html?method=signEvent&host=example.com&id=test-${Date.now()}&nick=${encodeURIComponent(nick)}&event=${base64Event}`,
width,
height,
left,
top,
});
} }
async onResetExtension() { async onResetExtension() {

View File

@@ -1,7 +1,14 @@
<div class="sam-text-header"> <div class="sam-text-header">
<button class="lock-btn" title="Lock" (click)="onClickLock()"> <div class="header-buttons">
<span class="emoji">🔒</span> <button class="header-btn" title="Lock" (click)="onClickLock()">
</button> <span class="emoji">🔒</span>
</button>
@if (devMode) {
<button class="header-btn" title="Test Permission Prompt" (click)="onTestPrompt()">
<span class="emoji"></span>
</button>
}
</div>
@if (showBackButton) { @if (showBackButton) {
<button class="back-btn" title="Go Back" (click)="goBack()"> <button class="back-btn" title="Go Back" (click)="goBack()">
<span class="emoji"></span> <span class="emoji"></span>

View File

@@ -4,7 +4,7 @@ import { FormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { import {
LoggerService, LoggerService,
StorageService, NavComponent,
NwcService, NwcService,
NwcConnection_DECRYPTED, NwcConnection_DECRYPTED,
CashuService, CashuService,
@@ -35,9 +35,8 @@ type WalletSection =
styleUrl: './wallet.component.scss', styleUrl: './wallet.component.scss',
imports: [CommonModule, FormsModule], imports: [CommonModule, FormsModule],
}) })
export class WalletComponent implements OnInit, OnDestroy { export class WalletComponent extends NavComponent implements OnInit, OnDestroy {
readonly #logger = inject(LoggerService); readonly #logger = inject(LoggerService);
readonly #storage = inject(StorageService);
readonly #router = inject(Router); readonly #router = inject(Router);
readonly nwcService = inject(NwcService); readonly nwcService = inject(NwcService);
readonly cashuService = inject(CashuService); readonly cashuService = inject(CashuService);
@@ -195,7 +194,7 @@ export class WalletComponent implements OnInit, OnDestroy {
ngOnInit(): void { ngOnInit(): void {
// Load current sync flow setting // Load current sync flow setting
this.currentSyncFlow = this.#storage.getSyncFlow(); this.currentSyncFlow = this.storage.getSyncFlow();
// Refresh balances on init if we have connections // Refresh balances on init if we have connections
if (this.connections.length > 0) { if (this.connections.length > 0) {
@@ -937,7 +936,7 @@ export class WalletComponent implements OnInit, OnDestroy {
async onClickLock() { async onClickLock() {
this.#logger.logVaultLock(); this.#logger.logVaultLock();
await this.#storage.lockVault(); await this.storage.lockVault();
this.#router.navigateByUrl('/vault-login'); this.#router.navigateByUrl('/vault-login');
} }

View File

@@ -17,7 +17,7 @@ export class WhitelistedAppsComponent extends NavComponent {
@ViewChild('toast') toast!: ToastComponent; @ViewChild('toast') toast!: ToastComponent;
@ViewChild('confirm') confirm!: ConfirmComponent; @ViewChild('confirm') confirm!: ConfirmComponent;
readonly storage = inject(StorageService); override readonly storage = inject(StorageService);
readonly #router = inject(Router); readonly #router = inject(Router);
get whitelistedHosts(): string[] { get whitelistedHosts(): string[] {

View File

@@ -1,8 +1,29 @@
import { inject } from '@angular/core'; import { inject } from '@angular/core';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { StorageService } from '../services/storage/storage.service';
import { Buffer } from 'buffer';
declare const chrome: {
windows: {
create: (options: {
type: string;
url: string;
width: number;
height: number;
left: number;
top: number;
}) => void;
};
};
export class NavComponent { export class NavComponent {
readonly #router = inject(Router); readonly #router = inject(Router);
protected readonly storage = inject(StorageService);
devMode = false;
constructor() {
this.devMode = this.storage.getSignerMetaHandler().signerMetaData?.devMode ?? false;
}
navigateBack() { navigateBack() {
window.history.back(); window.history.back();
@@ -11,4 +32,32 @@ export class NavComponent {
navigate(path: string) { navigate(path: string) {
this.#router.navigate([path]); this.#router.navigate([path]);
} }
onTestPrompt() {
const testEvent = {
kind: 1,
content: 'This is a test note for permission prompt preview.',
tags: [],
created_at: Math.floor(Date.now() / 1000),
};
const base64Event = Buffer.from(JSON.stringify(testEvent, null, 2)).toString('base64');
const currentIdentity = this.storage.getBrowserSessionHandler().browserSessionData?.identities.find(
i => i.id === this.storage.getBrowserSessionHandler().browserSessionData?.selectedIdentityId
);
const nick = currentIdentity?.nick ?? 'Test Identity';
const width = 375;
const height = 600;
const left = Math.round((screen.width - width) / 2);
const top = Math.round((screen.height - height) / 2);
chrome.windows.create({
type: 'popup',
url: `prompt.html?method=signEvent&host=example.com&id=test-${Date.now()}&nick=${encodeURIComponent(nick)}&event=${base64Event}`,
width,
height,
left,
top,
});
}
} }

View File

@@ -9,7 +9,7 @@ export abstract class SignerMetaHandler {
#signerMetaData?: SignerMetaData; #signerMetaData?: SignerMetaData;
readonly metaProperties = ['syncFlow', 'vaultSnapshots', 'maxBackups', 'recklessMode', 'whitelistedHosts', 'bookmarks']; readonly metaProperties = ['syncFlow', 'vaultSnapshots', 'maxBackups', 'recklessMode', 'whitelistedHosts', 'bookmarks', 'devMode'];
readonly DEFAULT_MAX_BACKUPS = 5; readonly DEFAULT_MAX_BACKUPS = 5;
/** /**
* Load the full data from the storage. If the storage is used for storing * Load the full data from the storage. If the storage is used for storing
@@ -58,6 +58,21 @@ export abstract class SignerMetaHandler {
await this.saveFullData(this.#signerMetaData); await this.saveFullData(this.#signerMetaData);
} }
/**
* Sets dev mode and immediately saves it.
*/
async setDevMode(enabled: boolean): Promise<void> {
if (!this.#signerMetaData) {
this.#signerMetaData = {
devMode: enabled,
};
} else {
this.#signerMetaData.devMode = enabled;
}
await this.saveFullData(this.#signerMetaData);
}
/** /**
* Adds a host to the whitelist and immediately saves it. * Adds a host to the whitelist and immediately saves it.
*/ */

View File

@@ -202,6 +202,9 @@ export interface SignerMetaData {
// User bookmarks // User bookmarks
bookmarks?: Bookmark[]; bookmarks?: Bookmark[];
// Dev mode: show test permission prompt button in settings
devMode?: boolean;
} }
/** /**

View File

@@ -16,9 +16,16 @@
letter-spacing: 0.1rem; letter-spacing: 0.1rem;
} }
.lock-btn { .header-buttons {
position: absolute; position: absolute;
left: 0; left: 0;
display: flex;
flex-direction: row;
align-items: center;
}
.lock-btn,
.header-btn {
background: transparent; background: transparent;
border: none; border: none;
padding: 8px; padding: 8px;
@@ -37,6 +44,12 @@
font-size: 20px; font-size: 20px;
} }
} }
// For backwards compatibility with single lock-btn
> .lock-btn {
position: absolute;
left: 0;
}
} }
.sam-footer-grid-2 { .sam-footer-grid-2 {

View File

@@ -2,7 +2,7 @@
"manifest_version": 3, "manifest_version": 3,
"name": "Plebeian Signer", "name": "Plebeian Signer",
"description": "Nostr Identity Manager & Signer", "description": "Nostr Identity Manager & Signer",
"version": "1.0.10", "version": "1.0.11",
"homepage_url": "https://github.com/PlebeianApp/plebeian-signer", "homepage_url": "https://github.com/PlebeianApp/plebeian-signer",
"options_page": "options.html", "options_page": "options.html",
"permissions": [ "permissions": [

View File

@@ -27,11 +27,66 @@
.page { .page {
height: 100%; height: 100%;
display: grid; display: grid;
grid-template-rows: 1fr 60px; grid-template-rows: 1fr auto;
grid-template-columns: 1fr; grid-template-columns: 1fr;
overflow-y: hidden; overflow-y: hidden;
} }
.actions {
display: flex;
flex-direction: column;
gap: 8px;
padding: var(--size);
background: var(--background);
}
.action-row {
display: flex;
align-items: center;
gap: 8px;
}
.action-label {
width: 60px;
font-size: 13px;
font-weight: 500;
color: var(--muted-foreground);
}
.action-buttons {
display: flex;
gap: 8px;
flex: 1;
}
.action-buttons button {
flex: 1;
padding: 8px 12px;
border-radius: 6px;
font-size: 13px;
font-weight: 500;
cursor: pointer;
border: none;
}
.btn-reject {
background: var(--muted);
color: var(--foreground);
}
.btn-reject:hover {
background: var(--border);
}
.btn-accept {
background: var(--primary);
color: var(--primary-foreground);
}
.btn-accept:hover {
opacity: 0.9;
}
.card { .card {
padding: var(--size); padding: var(--size);
background: var(--background-light); background: var(--background-light);
@@ -54,6 +109,12 @@
font-size: 12px; font-size: 12px;
color: gray; color: gray;
} }
.description {
margin: 0;
text-align: center;
line-height: 1.5;
}
</style> </style>
</head> </head>
<body> <body>
@@ -63,64 +124,31 @@
<span id="titleSpan" style="font-weight: 400 !important"></span> <span id="titleSpan" style="font-weight: 400 !important"></span>
</div> </div>
<span
class="host-INSERT sam-align-self-center sam-text-muted"
style="font-weight: 500"
></span>
<!-- Card for getPublicKey --> <!-- Card for getPublicKey -->
<div id="cardGetPublicKey" class="card sam-mt sam-ml sam-mr"> <div id="cardGetPublicKey" class="card sam-mt sam-ml sam-mr">
<span style="text-align: center"> <p class="description">
<b><span class="host-INSERT color-primary"></span></b> <b class="host-INSERT color-primary"></b> is requesting permission to
is requesting permission to<br /> <b class="color-primary">read your public key</b> for the selected identity
<br /> <b class="nick-INSERT color-primary"></b>.
<b class="color-primary">read your public key</b> <br /> </p>
<br />
<span>
for the selected identity
<span
style="font-weight: 500"
class="nick-INSERT color-primary"
></span>
</span>
</span>
</div> </div>
<!-- Card for getRelays --> <!-- Card for getRelays -->
<div id="cardGetRelays" class="card sam-mt sam-ml sam-mr"> <div id="cardGetRelays" class="card sam-mt sam-ml sam-mr">
<span style="text-align: center"> <p class="description">
<b><span class="host-INSERT color-primary"></span></b> <b class="host-INSERT color-primary"></b> is requesting permission to
is requesting permission to<br /> <b class="color-primary">read your relays</b> for the selected identity
<br /> <b class="nick-INSERT color-primary"></b>.
<b class="color-primary">read your relays</b> <br /> </p>
<br />
<span>
for the selected identity
<span
style="font-weight: 500"
class="nick-INSERT color-primary"
></span>
</span>
</span>
</div> </div>
<!-- Card for signEvent --> <!-- Card for signEvent -->
<div id="cardSignEvent" class="card sam-mt sam-ml sam-mr"> <div id="cardSignEvent" class="card sam-mt sam-ml sam-mr">
<span style="text-align: center"> <p class="description">
<b><span class="host-INSERT color-primary"></span></b> <b class="host-INSERT color-primary"></b> is requesting permission to
is requesting permission to<br /> <b class="color-primary">sign an event</b> (kind <span id="kindSpan"></span>)
<br /> for the selected identity <b class="nick-INSERT color-primary"></b>.
<b class="color-primary">sign an event</b> (kind </p>
<span id="kindSpan"></span>) <br />
<br />
<span>
for the selected identity
<span
style="font-weight: 500"
class="nick-INSERT color-primary"
></span>
</span>
</span>
</div> </div>
<!-- Card2 for signEvent --> <!-- Card2 for signEvent -->
@@ -130,20 +158,11 @@
<!-- Card for nip04.encrypt --> <!-- Card for nip04.encrypt -->
<div id="cardNip04Encrypt" class="card sam-mt sam-ml sam-mr"> <div id="cardNip04Encrypt" class="card sam-mt sam-ml sam-mr">
<span style="text-align: center"> <p class="description">
<b><span class="host-INSERT color-primary"></span></b> <b class="host-INSERT color-primary"></b> is requesting permission to
is requesting permission to<br /> <b class="color-primary">encrypt a text</b> (NIP04) for the selected identity
<br /> <b class="nick-INSERT color-primary"></b>.
<b class="color-primary">encrypt a text</b> (NIP04) <br /> </p>
<br />
<span>
for the selected identity
<span
style="font-weight: 500"
class="nick-INSERT color-primary"
></span>
</span>
</span>
</div> </div>
<!-- Card2 for nip04.encrypt --> <!-- Card2 for nip04.encrypt -->
@@ -153,20 +172,11 @@
<!-- Card for nip44.encrypt --> <!-- Card for nip44.encrypt -->
<div id="cardNip44Encrypt" class="card sam-mt sam-ml sam-mr"> <div id="cardNip44Encrypt" class="card sam-mt sam-ml sam-mr">
<span style="text-align: center"> <p class="description">
<b><span class="host-INSERT color-primary"></span></b> <b class="host-INSERT color-primary"></b> is requesting permission to
is requesting permission to<br /> <b class="color-primary">encrypt a text</b> (NIP44) for the selected identity
<br /> <b class="nick-INSERT color-primary"></b>.
<b class="color-primary">encrypt a text</b> (NIP44) <br /> </p>
<br />
<span>
for the selected identity
<span
style="font-weight: 500"
class="nick-INSERT color-primary"
></span>
</span>
</span>
</div> </div>
<!-- Card2 for nip44.encrypt --> <!-- Card2 for nip44.encrypt -->
@@ -176,20 +186,11 @@
<!-- Card for nip04.decrypt --> <!-- Card for nip04.decrypt -->
<div id="cardNip04Decrypt" class="card sam-mt sam-ml sam-mr"> <div id="cardNip04Decrypt" class="card sam-mt sam-ml sam-mr">
<span style="text-align: center"> <p class="description">
<b><span class="host-INSERT color-primary"></span></b> <b class="host-INSERT color-primary"></b> is requesting permission to
is requesting permission to<br /> <b class="color-primary">decrypt a text</b> (NIP04) for the selected identity
<br /> <b class="nick-INSERT color-primary"></b>.
<b class="color-primary">decrypt a text</b> (NIP04) <br /> </p>
<br />
<span>
for the selected identity
<span
style="font-weight: 500"
class="nick-INSERT color-primary"
></span>
</span>
</span>
</div> </div>
<!-- Card2 for nip04.decrypt --> <!-- Card2 for nip04.decrypt -->
@@ -199,20 +200,11 @@
<!-- Card for nip44.decrypt --> <!-- Card for nip44.decrypt -->
<div id="cardNip44Decrypt" class="card sam-mt sam-ml sam-mr"> <div id="cardNip44Decrypt" class="card sam-mt sam-ml sam-mr">
<span style="text-align: center"> <p class="description">
<b><span class="host-INSERT color-primary"></span></b> <b class="host-INSERT color-primary"></b> is requesting permission to
is requesting permission to<br /> <b class="color-primary">decrypt a text</b> (NIP44) for the selected identity
<br /> <b class="nick-INSERT color-primary"></b>.
<b class="color-primary">decrypt a text</b> (NIP44) <br /> </p>
<br />
<span>
for the selected identity
<span
style="font-weight: 500"
class="nick-INSERT color-primary"
></span>
</span>
</span>
</div> </div>
<!-- Card2 for nip44.decrypt --> <!-- Card2 for nip44.decrypt -->
@@ -224,47 +216,20 @@
<!-------------> <!------------->
<!-- ACTIONS --> <!-- ACTIONS -->
<!-------------> <!------------->
<div class="sam-footer-grid-2"> <div class="actions">
<div class="btn-group"> <div class="action-row">
<button id="rejectOnceButton" type="button" class="btn btn-secondary"> <span class="action-label">Reject</span>
Reject <div class="action-buttons">
</button> <button id="rejectOnceButton" type="button" class="btn-reject">Once</button>
<button <button id="rejectAlwaysButton" type="button" class="btn-reject">Always</button>
type="button" </div>
class="btn btn-secondary dropdown-toggle dropdown-toggle-split"
data-bs-toggle="dropdown"
aria-expanded="false"
>
<span class="visually-hidden">Toggle Dropdown</span>
</button>
<ul class="dropdown-menu dropdown-menu-end">
<li>
<button id="rejectAlwaysButton" class="dropdown-item">
Reject Always
</button>
</li>
</ul>
</div> </div>
<div class="action-row">
<div class="btn-group"> <span class="action-label">Accept</span>
<button id="approveAlwaysButton" type="button" class="btn btn-primary"> <div class="action-buttons">
Approve Always <button id="approveOnceButton" type="button" class="btn-accept">Once</button>
</button> <button id="approveAlwaysButton" type="button" class="btn-accept">Always</button>
<button </div>
type="button"
class="btn btn-primary dropdown-toggle dropdown-toggle-split"
data-bs-toggle="dropdown"
aria-expanded="false"
>
<span class="visually-hidden">Toggle Dropdown</span>
</button>
<ul class="dropdown-menu">
<li>
<button id="approveOnceButton" class="dropdown-item">
Approve Once
</button>
</li>
</ul>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,7 +1,14 @@
<div class="sam-text-header"> <div class="sam-text-header">
<button class="lock-btn" title="Lock" (click)="onClickLock()"> <div class="header-buttons">
<span class="emoji">🔒</span> <button class="header-btn" title="Lock" (click)="onClickLock()">
</button> <span class="emoji">🔒</span>
</button>
@if (devMode) {
<button class="header-btn" title="Test Permission Prompt" (click)="onTestPrompt()">
<span class="emoji"></span>
</button>
}
</div>
<button class="back-btn" title="Go Back" (click)="goBack()"> <button class="back-btn" title="Go Back" (click)="goBack()">
<span class="emoji"></span> <span class="emoji"></span>
</button> </button>

View File

@@ -3,9 +3,9 @@ import { Router } from '@angular/router';
import { import {
ConfirmComponent, ConfirmComponent,
LoggerService, LoggerService,
NavComponent,
SignerMetaData_VaultSnapshot, SignerMetaData_VaultSnapshot,
StartupService, StartupService,
StorageService,
} from '@common'; } from '@common';
import { getNewStorageServiceConfig } from '../../../common/data/get-new-storage-service-config'; import { getNewStorageServiceConfig } from '../../../common/data/get-new-storage-service-config';
@@ -15,9 +15,8 @@ import { getNewStorageServiceConfig } from '../../../common/data/get-new-storage
styleUrl: './backups.component.scss', styleUrl: './backups.component.scss',
imports: [ConfirmComponent], imports: [ConfirmComponent],
}) })
export class BackupsComponent implements OnInit { export class BackupsComponent extends NavComponent implements OnInit {
readonly #router = inject(Router); readonly #router = inject(Router);
readonly #storage = inject(StorageService);
readonly #startup = inject(StartupService); readonly #startup = inject(StartupService);
readonly #logger = inject(LoggerService); readonly #logger = inject(LoggerService);
@@ -27,11 +26,11 @@ export class BackupsComponent implements OnInit {
ngOnInit(): void { ngOnInit(): void {
this.loadBackups(); this.loadBackups();
this.maxBackups = this.#storage.getSignerMetaHandler().getMaxBackups(); this.maxBackups = this.storage.getSignerMetaHandler().getMaxBackups();
} }
loadBackups(): void { loadBackups(): void {
this.backups = this.#storage.getSignerMetaHandler().getBackups(); this.backups = this.storage.getSignerMetaHandler().getBackups();
} }
async onMaxBackupsChange(event: Event): Promise<void> { async onMaxBackupsChange(event: Event): Promise<void> {
@@ -39,14 +38,14 @@ export class BackupsComponent implements OnInit {
const value = parseInt(input.value, 10); const value = parseInt(input.value, 10);
if (!isNaN(value) && value >= 1 && value <= 20) { if (!isNaN(value) && value >= 1 && value <= 20) {
this.maxBackups = value; this.maxBackups = value;
await this.#storage.getSignerMetaHandler().setMaxBackups(value); await this.storage.getSignerMetaHandler().setMaxBackups(value);
} }
} }
async createManualBackup(): Promise<void> { async createManualBackup(): Promise<void> {
const browserSyncData = this.#storage.getBrowserSyncHandler().browserSyncData; const browserSyncData = this.storage.getBrowserSyncHandler().browserSyncData;
if (browserSyncData) { if (browserSyncData) {
await this.#storage.getSignerMetaHandler().createBackup(browserSyncData, 'manual'); await this.storage.getSignerMetaHandler().createBackup(browserSyncData, 'manual');
this.loadBackups(); this.loadBackups();
} }
} }
@@ -55,22 +54,22 @@ export class BackupsComponent implements OnInit {
this.restoringBackupId = backupId; this.restoringBackupId = backupId;
try { try {
// First, create a pre-restore backup of current state // First, create a pre-restore backup of current state
const currentData = this.#storage.getBrowserSyncHandler().browserSyncData; const currentData = this.storage.getBrowserSyncHandler().browserSyncData;
if (currentData) { if (currentData) {
await this.#storage.getSignerMetaHandler().createBackup(currentData, 'pre-restore'); await this.storage.getSignerMetaHandler().createBackup(currentData, 'pre-restore');
} }
// Get the backup data // Get the backup data
const backupData = this.#storage.getSignerMetaHandler().getBackupData(backupId); const backupData = this.storage.getSignerMetaHandler().getBackupData(backupId);
if (!backupData) { if (!backupData) {
throw new Error('Backup not found'); throw new Error('Backup not found');
} }
// Import the backup // Import the backup
await this.#storage.deleteVault(true); await this.storage.deleteVault(true);
await this.#storage.importVault(backupData); await this.storage.importVault(backupData);
this.#logger.logVaultImport('Backup Restore'); this.#logger.logVaultImport('Backup Restore');
this.#storage.isInitialized = false; this.storage.isInitialized = false;
this.#startup.startOver(getNewStorageServiceConfig()); this.#startup.startOver(getNewStorageServiceConfig());
} catch (error) { } catch (error) {
console.error('Failed to restore backup:', error); console.error('Failed to restore backup:', error);
@@ -79,7 +78,7 @@ export class BackupsComponent implements OnInit {
} }
async deleteBackup(backupId: string): Promise<void> { async deleteBackup(backupId: string): Promise<void> {
await this.#storage.getSignerMetaHandler().deleteBackup(backupId); await this.storage.getSignerMetaHandler().deleteBackup(backupId);
this.loadBackups(); this.loadBackups();
} }
@@ -120,7 +119,7 @@ export class BackupsComponent implements OnInit {
async onClickLock(): Promise<void> { async onClickLock(): Promise<void> {
this.#logger.logVaultLock(); this.#logger.logVaultLock();
await this.#storage.lockVault(); await this.storage.lockVault();
this.#router.navigateByUrl('/vault-login'); this.#router.navigateByUrl('/vault-login');
} }
} }

View File

@@ -1,9 +1,16 @@
<!-- eslint-disable @angular-eslint/template/interactive-supports-focus --> <!-- eslint-disable @angular-eslint/template/interactive-supports-focus -->
<!-- eslint-disable @angular-eslint/template/click-events-have-key-events --> <!-- eslint-disable @angular-eslint/template/click-events-have-key-events -->
<div class="sam-text-header"> <div class="sam-text-header">
<button class="lock-btn" title="Lock" (click)="onClickLock()"> <div class="header-buttons">
<span class="emoji">🔒</span> <button class="header-btn" title="Lock" (click)="onClickLock()">
</button> <span class="emoji">🔒</span>
</button>
@if (devMode) {
<button class="header-btn" title="Test Permission Prompt" (click)="onTestPrompt()">
<span class="emoji"></span>
</button>
}
</div>
<span>Bookmarks</span> <span>Bookmarks</span>
<button class="add-btn" title="Bookmark This Page" (click)="onBookmarkThisPage()"> <button class="add-btn" title="Bookmark This Page" (click)="onBookmarkThisPage()">
<span class="emoji"></span> <span class="emoji"></span>

View File

@@ -1,6 +1,6 @@
import { Component, inject, OnInit } from '@angular/core'; import { Component, inject, OnInit } from '@angular/core';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { Bookmark, LoggerService, SignerMetaData, StorageService } from '@common'; import { Bookmark, LoggerService, NavComponent, SignerMetaData } from '@common';
import { FirefoxMetaHandler } from '../../../common/data/firefox-meta-handler'; import { FirefoxMetaHandler } from '../../../common/data/firefox-meta-handler';
import browser from 'webextension-polyfill'; import browser from 'webextension-polyfill';
@@ -10,10 +10,9 @@ import browser from 'webextension-polyfill';
styleUrl: './bookmarks.component.scss', styleUrl: './bookmarks.component.scss',
imports: [], imports: [],
}) })
export class BookmarksComponent implements OnInit { export class BookmarksComponent extends NavComponent implements OnInit {
readonly #logger = inject(LoggerService); readonly #logger = inject(LoggerService);
readonly #metaHandler = new FirefoxMetaHandler(); readonly #metaHandler = new FirefoxMetaHandler();
readonly #storage = inject(StorageService);
readonly #router = inject(Router); readonly #router = inject(Router);
bookmarks: Bookmark[] = []; bookmarks: Bookmark[] = [];
@@ -94,7 +93,7 @@ export class BookmarksComponent implements OnInit {
async onClickLock() { async onClickLock() {
this.#logger.logVaultLock(); this.#logger.logVaultLock();
await this.#storage.lockVault(); await this.storage.lockVault();
this.#router.navigateByUrl('/vault-login'); this.#router.navigateByUrl('/vault-login');
} }
} }

View File

@@ -1,9 +1,16 @@
<!-- eslint-disable @angular-eslint/template/interactive-supports-focus --> <!-- eslint-disable @angular-eslint/template/interactive-supports-focus -->
<!-- eslint-disable @angular-eslint/template/click-events-have-key-events --> <!-- eslint-disable @angular-eslint/template/click-events-have-key-events -->
<div class="custom-header" style="position: sticky; top: 0"> <div class="custom-header" style="position: sticky; top: 0">
<button class="lock-btn" title="Lock" (click)="onClickLock()"> <div class="header-buttons">
<span class="emoji">🔒</span> <button class="header-btn" title="Lock" (click)="onClickLock()">
</button> <span class="emoji">🔒</span>
</button>
@if (devMode) {
<button class="header-btn" title="Test Permission Prompt" (click)="onTestPrompt()">
<span class="emoji"></span>
</button>
}
</div>
<span class="text">Identities</span> <span class="text">Identities</span>
<button class="add-btn" title="New Identity" (click)="onClickNewIdentity()"> <button class="add-btn" title="New Identity" (click)="onClickNewIdentity()">

View File

@@ -19,9 +19,16 @@
background: var(--background); background: var(--background);
position: relative; position: relative;
.lock-btn, .header-buttons {
.add-btn {
position: absolute; position: absolute;
left: 0;
display: flex;
flex-direction: row;
align-items: center;
}
.header-btn,
.add-btn {
background: transparent; background: transparent;
border: none; border: none;
padding: 8px; padding: 8px;
@@ -41,11 +48,8 @@
} }
} }
.lock-btn {
left: 0;
}
.add-btn { .add-btn {
position: absolute;
right: 0; right: 0;
} }

View File

@@ -4,6 +4,7 @@ import {
IconButtonComponent, IconButtonComponent,
Identity_DECRYPTED, Identity_DECRYPTED,
LoggerService, LoggerService,
NavComponent,
NostrHelper, NostrHelper,
ProfileMetadata, ProfileMetadata,
ProfileMetadataService, ProfileMetadataService,
@@ -17,8 +18,8 @@ import {
templateUrl: './identities.component.html', templateUrl: './identities.component.html',
styleUrl: './identities.component.scss', styleUrl: './identities.component.scss',
}) })
export class IdentitiesComponent implements OnInit { export class IdentitiesComponent extends NavComponent implements OnInit {
readonly storage = inject(StorageService); override readonly storage = inject(StorageService);
readonly #router = inject(Router); readonly #router = inject(Router);
readonly #profileMetadata = inject(ProfileMetadataService); readonly #profileMetadata = inject(ProfileMetadataService);
readonly #logger = inject(LoggerService); readonly #logger = inject(LoggerService);

View File

@@ -1,9 +1,16 @@
<!-- eslint-disable @angular-eslint/template/interactive-supports-focus --> <!-- eslint-disable @angular-eslint/template/interactive-supports-focus -->
<!-- eslint-disable @angular-eslint/template/click-events-have-key-events --> <!-- eslint-disable @angular-eslint/template/click-events-have-key-events -->
<div class="sam-text-header"> <div class="sam-text-header">
<button class="lock-btn" title="Lock" (click)="onClickLock()"> <div class="header-buttons">
<span class="emoji">🔒</span> <button class="header-btn" title="Lock" (click)="onClickLock()">
</button> <span class="emoji">🔒</span>
</button>
@if (devMode) {
<button class="header-btn" title="Test Permission Prompt" (click)="onTestPrompt()">
<span class="emoji"></span>
</button>
}
</div>
<span>You</span> <span>You</span>
<button class="edit-btn" title="Edit profile" (click)="onClickEditProfile()"> <button class="edit-btn" title="Edit profile" (click)="onClickEditProfile()">
<span class="emoji">📝</span> <span class="emoji">📝</span>

View File

@@ -3,11 +3,11 @@ import { Router } from '@angular/router';
import { import {
Identity_DECRYPTED, Identity_DECRYPTED,
LoggerService, LoggerService,
NavComponent,
NostrHelper, NostrHelper,
ProfileMetadata, ProfileMetadata,
ProfileMetadataService, ProfileMetadataService,
PubkeyComponent, PubkeyComponent,
StorageService,
ToastComponent, ToastComponent,
VisualNip05Pipe, VisualNip05Pipe,
validateNip05, validateNip05,
@@ -19,7 +19,7 @@ import {
templateUrl: './identity.component.html', templateUrl: './identity.component.html',
styleUrl: './identity.component.scss', styleUrl: './identity.component.scss',
}) })
export class IdentityComponent implements OnInit { export class IdentityComponent extends NavComponent implements OnInit {
selectedIdentity: Identity_DECRYPTED | undefined; selectedIdentity: Identity_DECRYPTED | undefined;
selectedIdentityNpub: string | undefined; selectedIdentityNpub: string | undefined;
profile: ProfileMetadata | null = null; profile: ProfileMetadata | null = null;
@@ -27,7 +27,6 @@ export class IdentityComponent implements OnInit {
validating = false; validating = false;
loading = true; loading = true;
readonly #storage = inject(StorageService);
readonly #router = inject(Router); readonly #router = inject(Router);
readonly #profileMetadata = inject(ProfileMetadataService); readonly #profileMetadata = inject(ProfileMetadataService);
readonly #logger = inject(LoggerService); readonly #logger = inject(LoggerService);
@@ -82,17 +81,17 @@ export class IdentityComponent implements OnInit {
async onClickLock() { async onClickLock() {
this.#logger.logVaultLock(); this.#logger.logVaultLock();
await this.#storage.lockVault(); await this.storage.lockVault();
this.#router.navigateByUrl('/vault-login'); this.#router.navigateByUrl('/vault-login');
} }
async #loadData() { async #loadData() {
try { try {
const selectedIdentityId = const selectedIdentityId =
this.#storage.getBrowserSessionHandler().browserSessionData this.storage.getBrowserSessionHandler().browserSessionData
?.selectedIdentityId ?? null; ?.selectedIdentityId ?? null;
const identity = this.#storage const identity = this.storage
.getBrowserSessionHandler() .getBrowserSessionHandler()
.browserSessionData?.identities.find( .browserSessionData?.identities.find(
(x) => x.id === selectedIdentityId (x) => x.id === selectedIdentityId

View File

@@ -1,7 +1,14 @@
<div class="sam-text-header"> <div class="sam-text-header">
<button class="lock-btn" title="Lock" (click)="onClickLock()"> <div class="header-buttons">
<span class="emoji">🔒</span> <button class="header-btn" title="Lock" (click)="onClickLock()">
</button> <span class="emoji">🔒</span>
</button>
@if (devMode) {
<button class="header-btn" title="Test Permission Prompt" (click)="onTestPrompt()">
<span class="emoji"></span>
</button>
}
</div>
<span> Plebeian Signer </span> <span> Plebeian Signer </span>
</div> </div>

View File

@@ -1,6 +1,6 @@
import { Component, inject } from '@angular/core'; import { Component, inject } from '@angular/core';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { LoggerService, StorageService } from '@common'; import { LoggerService, NavComponent } from '@common';
import packageJson from '../../../../../../../package.json'; import packageJson from '../../../../../../../package.json';
@Component({ @Component({
@@ -8,16 +8,15 @@ import packageJson from '../../../../../../../package.json';
templateUrl: './info.component.html', templateUrl: './info.component.html',
styleUrl: './info.component.scss', styleUrl: './info.component.scss',
}) })
export class InfoComponent { export class InfoComponent extends NavComponent {
readonly #logger = inject(LoggerService); readonly #logger = inject(LoggerService);
readonly #storage = inject(StorageService);
readonly #router = inject(Router); readonly #router = inject(Router);
version = packageJson.custom.firefox.version; version = packageJson.custom.firefox.version;
async onClickLock() { async onClickLock() {
this.#logger.logVaultLock(); this.#logger.logVaultLock();
await this.#storage.lockVault(); await this.storage.lockVault();
this.#router.navigateByUrl('/vault-login'); this.#router.navigateByUrl('/vault-login');
} }
} }

View File

@@ -1,7 +1,14 @@
<div class="sam-text-header"> <div class="sam-text-header">
<button class="lock-btn" title="Lock" (click)="onClickLock()"> <div class="header-buttons">
<span class="emoji">🔒</span> <button class="header-btn" title="Lock" (click)="onClickLock()">
</button> <span class="emoji">🔒</span>
</button>
@if (devMode) {
<button class="header-btn" title="Test Permission Prompt" (click)="onTestPrompt()">
<span class="emoji"></span>
</button>
}
</div>
<span>Logs</span> <span>Logs</span>
<div class="logs-actions"> <div class="logs-actions">
<button class="btn btn-sm btn-secondary" title="Refresh logs" (click)="onRefresh()">Refresh</button> <button class="btn btn-sm btn-secondary" title="Refresh logs" (click)="onRefresh()">Refresh</button>

View File

@@ -1,6 +1,6 @@
import { Component, inject, OnInit } from '@angular/core'; import { Component, inject, OnInit } from '@angular/core';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { LoggerService, LogEntry, StorageService } from '@common'; import { LoggerService, LogEntry, NavComponent } from '@common';
import { DatePipe } from '@angular/common'; import { DatePipe } from '@angular/common';
@Component({ @Component({
@@ -9,9 +9,8 @@ import { DatePipe } from '@angular/common';
styleUrl: './logs.component.scss', styleUrl: './logs.component.scss',
imports: [DatePipe], imports: [DatePipe],
}) })
export class LogsComponent implements OnInit { export class LogsComponent extends NavComponent implements OnInit {
readonly #logger = inject(LoggerService); readonly #logger = inject(LoggerService);
readonly #storage = inject(StorageService);
readonly #router = inject(Router); readonly #router = inject(Router);
get logs(): LogEntry[] { get logs(): LogEntry[] {
@@ -46,7 +45,7 @@ export class LogsComponent implements OnInit {
async onClickLock() { async onClickLock() {
this.#logger.logVaultLock(); this.#logger.logVaultLock();
await this.#storage.lockVault(); await this.storage.lockVault();
this.#router.navigateByUrl('/vault-login'); this.#router.navigateByUrl('/vault-login');
} }
} }

View File

@@ -1,10 +1,39 @@
<div class="sam-text-header"> <div class="sam-text-header">
<button class="lock-btn" title="Lock" (click)="onClickLock()"> <div class="header-buttons">
<span class="emoji">🔒</span> <button class="header-btn" title="Lock" (click)="onClickLock()">
</button> <span class="emoji">🔒</span>
</button>
@if (devMode) {
<button class="header-btn" title="Test Permission Prompt" (click)="onTestPrompt()">
<span class="emoji"></span>
</button>
}
</div>
<span> Settings </span> <span> Settings </span>
</div> </div>
<div class="vault-buttons">
<button class="btn btn-primary" (click)="onClickExportVault()">
Export Vault
</button>
<button class="btn btn-primary" (click)="navigate('/vault-import')">
Import Vault
</button>
</div>
<lib-nav-item text="💾 Backups" (click)="navigate('/home/backups')"></lib-nav-item>
<lib-nav-item text="🪵 Logs" (click)="navigate('/home/logs')"></lib-nav-item>
<lib-nav-item text="💡 Info" (click)="navigate('/home/info')"></lib-nav-item>
<div class="dev-mode-row">
<label class="toggle-label">
<input type="checkbox" [checked]="devMode" (change)="onToggleDevMode($event)" />
<span>Dev Mode</span>
</label>
</div>
<div class="sam-flex-grow"></div>
<div class="sync-info"> <div class="sync-info">
<span class="sync-label">SYNC: {{ syncFlow }}</span> <span class="sync-label">SYNC: {{ syncFlow }}</span>
<p class="sync-note"> <p class="sync-note">
@@ -13,25 +42,11 @@
</p> </p>
</div> </div>
<button class="btn btn-primary" (click)="onClickExportVault()">
Export Vault
</button>
<button class="btn btn-primary" (click)="navigate('/vault-import')">
Import Vault
</button>
<lib-nav-item text="💾 Backups" (click)="navigate('/home/backups')"></lib-nav-item>
<lib-nav-item text="🪵 Logs" (click)="navigate('/home/logs')"></lib-nav-item>
<lib-nav-item text="💡 Info" (click)="navigate('/home/info')"></lib-nav-item>
<div class="sam-flex-grow"></div>
<button <button
class="btn btn-danger" class="btn btn-danger"
(click)=" (click)="
confirm.show( confirm.show(
'Do you really want to reset your extension? All data will be lost.', 'Do you really want to reset your extension? Every data will be lost.',
onResetExtension.bind(this) onResetExtension.bind(this)
) )
" "

View File

@@ -16,6 +16,35 @@
} }
} }
.vault-buttons {
display: flex;
gap: var(--size);
button {
flex: 1;
}
}
.dev-mode-row {
display: flex;
align-items: center;
gap: var(--size);
.toggle-label {
display: flex;
align-items: center;
gap: var(--size-h);
cursor: pointer;
font-size: 0.9rem;
input[type="checkbox"] {
width: 16px;
height: 16px;
cursor: pointer;
}
}
}
.sync-info { .sync-info {
.sync-label { .sync-label {
display: block; display: block;

View File

@@ -11,6 +11,8 @@ import {
StorageService, StorageService,
} from '@common'; } from '@common';
import { getNewStorageServiceConfig } from '../../../common/data/get-new-storage-service-config'; import { getNewStorageServiceConfig } from '../../../common/data/get-new-storage-service-config';
import { Buffer } from 'buffer';
import browser from 'webextension-polyfill';
@Component({ @Component({
selector: 'app-settings', selector: 'app-settings',
@@ -21,6 +23,7 @@ import { getNewStorageServiceConfig } from '../../../common/data/get-new-storage
export class SettingsComponent extends NavComponent implements OnInit { export class SettingsComponent extends NavComponent implements OnInit {
readonly #router = inject(Router); readonly #router = inject(Router);
syncFlow: string | undefined; syncFlow: string | undefined;
override devMode = false;
readonly #storage = inject(StorageService); readonly #storage = inject(StorageService);
readonly #startup = inject(StartupService); readonly #startup = inject(StartupService);
@@ -44,6 +47,44 @@ export class SettingsComponent extends NavComponent implements OnInit {
default: default:
break; break;
} }
// Load dev mode setting
this.devMode = this.#storage.getSignerMetaHandler().signerMetaData?.devMode ?? false;
}
async onToggleDevMode(event: Event) {
const checked = (event.target as HTMLInputElement).checked;
this.devMode = checked;
await this.#storage.getSignerMetaHandler().setDevMode(checked);
}
override async onTestPrompt() {
// Open a test permission prompt window
const testEvent = {
kind: 1,
content: 'This is a test note for permission prompt preview.',
tags: [],
created_at: Math.floor(Date.now() / 1000),
};
const base64Event = Buffer.from(JSON.stringify(testEvent, null, 2)).toString('base64');
const currentIdentity = this.#storage.getBrowserSessionHandler().browserSessionData?.identities.find(
i => i.id === this.#storage.getBrowserSessionHandler().browserSessionData?.selectedIdentityId
);
const nick = currentIdentity?.nick ?? 'Test Identity';
const width = 375;
const height = 600;
const left = Math.round((screen.width - width) / 2);
const top = Math.round((screen.height - height) / 2);
browser.windows.create({
type: 'popup',
url: `prompt.html?method=signEvent&host=example.com&id=test-${Date.now()}&nick=${encodeURIComponent(nick)}&event=${base64Event}`,
width,
height,
left,
top,
});
} }
async onResetExtension() { async onResetExtension() {

View File

@@ -1,7 +1,14 @@
<div class="sam-text-header"> <div class="sam-text-header">
<button class="lock-btn" title="Lock" (click)="onClickLock()"> <div class="header-buttons">
<span class="emoji">🔒</span> <button class="header-btn" title="Lock" (click)="onClickLock()">
</button> <span class="emoji">🔒</span>
</button>
@if (devMode) {
<button class="header-btn" title="Test Permission Prompt" (click)="onTestPrompt()">
<span class="emoji"></span>
</button>
}
</div>
@if (showBackButton) { @if (showBackButton) {
<button class="back-btn" title="Go Back" (click)="goBack()"> <button class="back-btn" title="Go Back" (click)="goBack()">
<span class="emoji"></span> <span class="emoji"></span>

View File

@@ -4,7 +4,7 @@ import { FormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { import {
LoggerService, LoggerService,
StorageService, NavComponent,
NwcService, NwcService,
NwcConnection_DECRYPTED, NwcConnection_DECRYPTED,
CashuService, CashuService,
@@ -35,9 +35,8 @@ type WalletSection =
styleUrl: './wallet.component.scss', styleUrl: './wallet.component.scss',
imports: [CommonModule, FormsModule], imports: [CommonModule, FormsModule],
}) })
export class WalletComponent implements OnInit, OnDestroy { export class WalletComponent extends NavComponent implements OnInit, OnDestroy {
readonly #logger = inject(LoggerService); readonly #logger = inject(LoggerService);
readonly #storage = inject(StorageService);
readonly #router = inject(Router); readonly #router = inject(Router);
readonly nwcService = inject(NwcService); readonly nwcService = inject(NwcService);
readonly cashuService = inject(CashuService); readonly cashuService = inject(CashuService);
@@ -195,7 +194,7 @@ export class WalletComponent implements OnInit, OnDestroy {
ngOnInit(): void { ngOnInit(): void {
// Load current sync flow setting // Load current sync flow setting
this.currentSyncFlow = this.#storage.getSyncFlow(); this.currentSyncFlow = this.storage.getSyncFlow();
// Refresh balances on init if we have connections // Refresh balances on init if we have connections
if (this.connections.length > 0) { if (this.connections.length > 0) {
@@ -937,7 +936,7 @@ export class WalletComponent implements OnInit, OnDestroy {
async onClickLock() { async onClickLock() {
this.#logger.logVaultLock(); this.#logger.logVaultLock();
await this.#storage.lockVault(); await this.storage.lockVault();
this.#router.navigateByUrl('/vault-login'); this.#router.navigateByUrl('/vault-login');
} }

View File

@@ -18,7 +18,7 @@ export class WhitelistedAppsComponent extends NavComponent {
@ViewChild('toast') toast!: ToastComponent; @ViewChild('toast') toast!: ToastComponent;
@ViewChild('confirm') confirm!: ConfirmComponent; @ViewChild('confirm') confirm!: ConfirmComponent;
readonly storage = inject(StorageService); override readonly storage = inject(StorageService);
readonly #router = inject(Router); readonly #router = inject(Router);
get whitelistedHosts(): string[] { get whitelistedHosts(): string[] {