Release v1.0.2 - Fix Buffer polyfill race condition in prompt

- Fix race condition where permission prompts failed on first request
  due to Buffer polyfill not being initialized during module evaluation
- Replace Buffer.from() with native browser APIs (atob + TextDecoder)
  in prompt.ts for reliable base64 decoding
- Add debug logging to reckless mode approval checks
- Update permission encryption to support v2 vault key format
- Enhance LoggerService with warn/error/debug methods and log storage
- Add logs component for viewing extension activity
- Simplify deriving modal component
- Rename icon files from gooti to plebian-signer
- Update permissions component with improved styling

Files modified:
- projects/chrome/src/prompt.ts
- projects/firefox/src/prompt.ts
- projects/*/src/background-common.ts
- projects/common/src/lib/services/logger/logger.service.ts
- projects/*/src/app/components/home/logs/ (new)
- projects/*/public/*.svg, *.png (renamed)

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-20 08:52:44 +01:00
parent 4b2d23e942
commit abd4a21f8f
36 changed files with 602 additions and 158 deletions

View File

@@ -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 }}

View File

@@ -17,6 +17,10 @@
top: 0;
}
.remove-all-btn {
margin-bottom: var(--size);
}
.permissions-card {
background: var(--background-light);
border-radius: 8px;

View File

@@ -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()

View File

@@ -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>

View File

@@ -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>

View File

@@ -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;
}

View File

@@ -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';
}
}
}