- Upgrade vault encryption from PBKDF2 (1000 iterations) to Argon2id
(256MB memory, 8 iterations, 4 threads, ~3 second derivation)
- Add automatic migration from v1 to v2 vault format on unlock
- Add WebAssembly CSP support for hash-wasm Argon2id implementation
- Add NIP-42 relay authentication support for auth-required relays
- Add profile edit feature with pencil icon on identity page
- Add direct NIP-05 validation (removes NDK dependency for validation)
- Add deriving modal with progress timer during key derivation
- Add client tag "plebeian-signer" to profile events
- Fix modal colors (dark theme for visibility)
- Fix NIP-05 badge styling to include check/error indicator
- Add release zip packages for Chrome and Firefox
New files:
- projects/common/src/lib/helpers/argon2-crypto.ts
- projects/common/src/lib/helpers/websocket-auth.ts
- projects/common/src/lib/helpers/nip05-validator.ts
- projects/common/src/lib/components/deriving-modal/
- projects/{chrome,firefox}/src/app/components/profile-edit/
- releases/plebeian-signer-{chrome,firefox}-v1.0.0.zip
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
151 lines
3.9 KiB
TypeScript
151 lines
3.9 KiB
TypeScript
import { Component, inject, OnInit } from '@angular/core';
|
|
import { Router } from '@angular/router';
|
|
import {
|
|
Identity_DECRYPTED,
|
|
NostrHelper,
|
|
ProfileMetadata,
|
|
ProfileMetadataService,
|
|
PubkeyComponent,
|
|
StorageService,
|
|
ToastComponent,
|
|
VisualNip05Pipe,
|
|
validateNip05,
|
|
} from '@common';
|
|
|
|
@Component({
|
|
selector: 'app-identity',
|
|
imports: [PubkeyComponent, VisualNip05Pipe, ToastComponent],
|
|
templateUrl: './identity.component.html',
|
|
styleUrl: './identity.component.scss',
|
|
})
|
|
export class IdentityComponent implements OnInit {
|
|
selectedIdentity: Identity_DECRYPTED | undefined;
|
|
selectedIdentityNpub: string | undefined;
|
|
profile: ProfileMetadata | null = null;
|
|
nip05isValidated: boolean | undefined;
|
|
validating = false;
|
|
loading = true;
|
|
|
|
readonly #storage = inject(StorageService);
|
|
readonly #router = inject(Router);
|
|
readonly #profileMetadata = inject(ProfileMetadataService);
|
|
|
|
ngOnInit(): void {
|
|
this.#loadData();
|
|
}
|
|
|
|
get displayName(): string | undefined {
|
|
return this.#profileMetadata.getDisplayName(this.profile);
|
|
}
|
|
|
|
get username(): string | undefined {
|
|
return this.#profileMetadata.getUsername(this.profile);
|
|
}
|
|
|
|
get avatarUrl(): string | undefined {
|
|
return this.profile?.picture;
|
|
}
|
|
|
|
get bannerUrl(): string | undefined {
|
|
return this.profile?.banner;
|
|
}
|
|
|
|
copyToClipboard(pubkey: string | undefined) {
|
|
if (!pubkey) {
|
|
return;
|
|
}
|
|
navigator.clipboard.writeText(pubkey);
|
|
}
|
|
|
|
onClickShowDetails() {
|
|
if (!this.selectedIdentity) {
|
|
return;
|
|
}
|
|
|
|
this.#router.navigateByUrl(
|
|
`/edit-identity/${this.selectedIdentity.id}/home`
|
|
);
|
|
}
|
|
|
|
onClickEditProfile() {
|
|
if (!this.selectedIdentity) {
|
|
return;
|
|
}
|
|
this.#router.navigateByUrl('/profile-edit');
|
|
}
|
|
|
|
async #loadData() {
|
|
try {
|
|
const selectedIdentityId =
|
|
this.#storage.getBrowserSessionHandler().browserSessionData
|
|
?.selectedIdentityId ?? null;
|
|
|
|
const identity = this.#storage
|
|
.getBrowserSessionHandler()
|
|
.browserSessionData?.identities.find(
|
|
(x) => x.id === selectedIdentityId
|
|
);
|
|
|
|
if (!identity) {
|
|
this.loading = false;
|
|
return;
|
|
}
|
|
|
|
this.selectedIdentity = identity;
|
|
const pubkey = NostrHelper.pubkeyFromPrivkey(identity.privkey);
|
|
this.selectedIdentityNpub = NostrHelper.pubkey2npub(pubkey);
|
|
|
|
// Initialize the profile metadata service (loads cache from storage)
|
|
await this.#profileMetadata.initialize();
|
|
|
|
// Check if we have cached profile data
|
|
const cachedProfile = this.#profileMetadata.getCachedProfile(pubkey);
|
|
if (cachedProfile) {
|
|
this.profile = cachedProfile;
|
|
this.loading = false;
|
|
// Validate NIP-05 if present (in background)
|
|
if (cachedProfile.nip05) {
|
|
this.#validateNip05(pubkey, cachedProfile.nip05);
|
|
}
|
|
return; // Use cached data, don't fetch again
|
|
}
|
|
|
|
// No cached data, fetch from relays
|
|
this.loading = true;
|
|
const fetchedProfile = await this.#profileMetadata.fetchProfile(pubkey);
|
|
if (fetchedProfile) {
|
|
this.profile = fetchedProfile;
|
|
// Validate NIP-05 if present
|
|
if (fetchedProfile.nip05) {
|
|
this.#validateNip05(pubkey, fetchedProfile.nip05);
|
|
}
|
|
}
|
|
|
|
this.loading = false;
|
|
} catch (error) {
|
|
console.error(error);
|
|
this.loading = false;
|
|
}
|
|
}
|
|
|
|
async #validateNip05(pubkey: string, nip05: string) {
|
|
try {
|
|
this.validating = true;
|
|
|
|
// Direct NIP-05 validation - fetches .well-known/nostr.json directly
|
|
const result = await validateNip05(nip05, pubkey);
|
|
this.nip05isValidated = result.valid;
|
|
|
|
if (!result.valid) {
|
|
console.log('NIP-05 validation failed:', result.error);
|
|
}
|
|
|
|
this.validating = false;
|
|
} catch (error) {
|
|
console.error('NIP-05 validation failed:', error);
|
|
this.nip05isValidated = false;
|
|
this.validating = false;
|
|
}
|
|
}
|
|
}
|