mirror of
https://github.com/irislib/iris-messenger.git
synced 2024-10-18 06:03:22 +00:00
Hex, PublicKey and EventID classes
This commit is contained in:
parent
de72141343
commit
a7ead39015
@ -183,7 +183,7 @@ class SearchBox extends Component<Props, State> {
|
||||
Key.getPubKeyByNip05Address(query).then((pubKey) => {
|
||||
// if query hasn't changed since we started the request
|
||||
if (pubKey && query === String(this.props.query || this.inputRef.current.value)) {
|
||||
this.props.onSelect?.({ key: pubKey });
|
||||
this.props.onSelect?.({ key: pubKey.toHex() });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -9,6 +9,8 @@ import {
|
||||
} from 'nostr-tools';
|
||||
import { route } from 'preact-router';
|
||||
|
||||
import { PublicKey } from '@/utils/Hex.ts';
|
||||
|
||||
import localState from '../state/LocalState.ts';
|
||||
import Helpers from '../utils/Helpers';
|
||||
|
||||
@ -193,14 +195,14 @@ export default {
|
||||
console.error(e);
|
||||
}
|
||||
},
|
||||
async getPubKeyByNip05Address(address: string): Promise<string | null> {
|
||||
async getPubKeyByNip05Address(address: string): Promise<PublicKey | null> {
|
||||
try {
|
||||
const [localPart, domain] = address.split('@');
|
||||
const url = `https://${domain}/.well-known/nostr.json?name=${localPart}`;
|
||||
const response = await fetch(url);
|
||||
const json = await response.json();
|
||||
const names = json.names;
|
||||
return names[localPart] || null;
|
||||
return new PublicKey(names[localPart]) || null;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return null;
|
||||
|
57
src/js/utils/Hex.test.ts
Normal file
57
src/js/utils/Hex.test.ts
Normal file
@ -0,0 +1,57 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { EventID, PublicKey } from '@/utils/Hex';
|
||||
|
||||
describe('PublicKey', () => {
|
||||
it('should convert npub bech32 to hex', () => {
|
||||
const bech32 = 'npub1g53mukxnjkcmr94fhryzkqutdz2ukq4ks0gvy5af25rgmwsl4ngq43drvk';
|
||||
const hex = '4523be58d395b1b196a9b8c82b038b6895cb02b683d0c253a955068dba1facd0';
|
||||
const publicKey = new PublicKey(bech32);
|
||||
expect(publicKey.toHex()).toEqual(hex);
|
||||
expect(publicKey.toBech32()).toEqual(bech32);
|
||||
});
|
||||
|
||||
it('should init from hex', () => {
|
||||
const hex = '4523be58d395b1b196a9b8c82b038b6895cb02b683d0c253a955068dba1facd0';
|
||||
const publicKey = new PublicKey(hex);
|
||||
expect(publicKey.toHex()).toEqual(hex);
|
||||
expect(publicKey.toBech32()).toEqual(
|
||||
'npub1g53mukxnjkcmr94fhryzkqutdz2ukq4ks0gvy5af25rgmwsl4ngq43drvk',
|
||||
);
|
||||
});
|
||||
|
||||
it('should fail with too long hex', () => {
|
||||
const hex =
|
||||
'4523be58d395b1b196a9b8c82b038b6895cb02b683d0c253a955068dba1facd04523be58d395b1b196a9b8c82b038b6895cb02b683d0c253a955068dba1facd0';
|
||||
expect(() => new PublicKey(hex)).toThrow();
|
||||
});
|
||||
|
||||
it('equals(hexStr)', () => {
|
||||
const hex = '4523be58d395b1b196a9b8c82b038b6895cb02b683d0c253a955068dba1facd0';
|
||||
const publicKey = new PublicKey(hex);
|
||||
expect(publicKey.equals(hex)).toEqual(true);
|
||||
});
|
||||
|
||||
it('equals(PublicKey)', () => {
|
||||
const hex = '4523be58d395b1b196a9b8c82b038b6895cb02b683d0c253a955068dba1facd0';
|
||||
const publicKey = new PublicKey(hex);
|
||||
const publicKey2 = new PublicKey(hex);
|
||||
expect(publicKey.equals(publicKey2)).toEqual(true);
|
||||
});
|
||||
|
||||
it('equals(bech32)', () => {
|
||||
const bech32 = 'npub1g53mukxnjkcmr94fhryzkqutdz2ukq4ks0gvy5af25rgmwsl4ngq43drvk';
|
||||
const publicKey = new PublicKey(bech32);
|
||||
expect(publicKey.equals(bech32)).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('EventID', () => {
|
||||
it('should convert note id bech32 to hex', () => {
|
||||
const noteBech32 = 'note1wdyajan9c9d72wanqe2l34lxgdu3q5esglhquusfkg34fqq6462qh4cjd5';
|
||||
const noteHex = '7349d97665c15be53bb30655f8d7e6437910533047ee0e7209b22354801aae94';
|
||||
const eventId = new EventID(noteBech32);
|
||||
expect(eventId.toHex()).toEqual(noteHex);
|
||||
expect(eventId.toBech32()).toEqual(noteBech32);
|
||||
});
|
||||
});
|
88
src/js/utils/Hex.ts
Normal file
88
src/js/utils/Hex.ts
Normal file
@ -0,0 +1,88 @@
|
||||
import * as bech32 from 'bech32-buffer';
|
||||
|
||||
import Helpers from '@/utils/Helpers.tsx';
|
||||
|
||||
function bech32ToHex(str: string): string {
|
||||
try {
|
||||
const { data } = bech32.decode(str);
|
||||
const addr = Helpers.arrayToHex(data);
|
||||
return addr;
|
||||
} catch (e) {
|
||||
throw new Error('The provided string is not a valid bech32 address: ' + str);
|
||||
}
|
||||
}
|
||||
|
||||
export class Hex {
|
||||
value: string;
|
||||
|
||||
constructor(str: string, expectedLength?: number) {
|
||||
this.validateHex(str, expectedLength);
|
||||
this.value = str;
|
||||
}
|
||||
|
||||
private validateHex(str: string, expectedLength?: number): void {
|
||||
if (!/^[0-9a-fA-F]+$/.test(str)) {
|
||||
throw new Error(`The provided string is not a valid hex value: "${str}"`);
|
||||
}
|
||||
|
||||
if (expectedLength && str.length !== expectedLength) {
|
||||
throw new Error(
|
||||
`The provided hex value does not match the expected length of ${expectedLength} characters: ${str}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
toBech32(prefix: string): string {
|
||||
if (!prefix) {
|
||||
throw new Error('prefix is required');
|
||||
}
|
||||
|
||||
const bytesArray = this.value.match(/.{1,2}/g);
|
||||
const bytes = new Uint8Array(bytesArray!.map((byte) => parseInt(byte, 16)));
|
||||
return bech32.encode(prefix, bytes);
|
||||
}
|
||||
|
||||
toHex(): string {
|
||||
return this.value;
|
||||
}
|
||||
}
|
||||
|
||||
export class EventID extends Hex {
|
||||
constructor(str: string) {
|
||||
if (str.startsWith('note')) {
|
||||
str = bech32ToHex(str);
|
||||
}
|
||||
super(str, 64);
|
||||
}
|
||||
|
||||
toBech32(): string {
|
||||
return super.toBech32('note');
|
||||
}
|
||||
|
||||
equals(other: EventID | string): boolean {
|
||||
if (typeof other === 'string') {
|
||||
other = new EventID(other);
|
||||
}
|
||||
return this.value === other.value;
|
||||
}
|
||||
}
|
||||
|
||||
export class PublicKey extends Hex {
|
||||
constructor(str: string) {
|
||||
if (str.startsWith('npub')) {
|
||||
str = bech32ToHex(str);
|
||||
}
|
||||
super(str, 64);
|
||||
}
|
||||
|
||||
toBech32(): string {
|
||||
return super.toBech32('npub');
|
||||
}
|
||||
|
||||
equals(other: PublicKey | string): boolean {
|
||||
if (typeof other === 'string') {
|
||||
other = new PublicKey(other);
|
||||
}
|
||||
return this.value === other.value;
|
||||
}
|
||||
}
|
@ -1,16 +1,16 @@
|
||||
import { useEffect } from 'preact/hooks';
|
||||
import { route } from 'preact-router';
|
||||
|
||||
import { EventID } from '@/utils/Hex.ts';
|
||||
import View from '@/views/View.tsx';
|
||||
|
||||
import CreateNoteForm from '../components/create/CreateNoteForm';
|
||||
import EventComponent from '../components/events/EventComponent';
|
||||
import Key from '../nostr/Key';
|
||||
import { translate as t } from '../translations/Translation.mjs';
|
||||
|
||||
const Note = (props) => {
|
||||
useEffect(() => {
|
||||
const nostrBech32Id = Key.toNostrBech32Address(props.id, 'note');
|
||||
const nostrBech32Id = new EventID(props.id).toBech32();
|
||||
if (nostrBech32Id && props.id !== nostrBech32Id) {
|
||||
route(`/${nostrBech32Id}`, true);
|
||||
return;
|
||||
|
@ -5,6 +5,7 @@ import SimpleImageModal from '@/components/modal/Image.tsx';
|
||||
import { useProfile } from '@/nostr/hooks/useProfile.ts';
|
||||
import { getEventReplyingTo, isRepost } from '@/nostr/utils.ts';
|
||||
import useLocalState from '@/state/useLocalState.ts';
|
||||
import { PublicKey } from '@/utils/Hex.ts';
|
||||
import ProfileHelmet from '@/views/profile/Helmet.tsx';
|
||||
|
||||
import Feed from '../../components/feed/Feed.tsx';
|
||||
@ -60,21 +61,20 @@ function Profile(props) {
|
||||
}, [profile]);
|
||||
|
||||
useEffect(() => {
|
||||
const pub = props.id;
|
||||
const npubComputed = Key.toNostrBech32Address(pub, 'npub');
|
||||
try {
|
||||
const pub = new PublicKey(props.id);
|
||||
const npubComputed = pub.toBech32();
|
||||
|
||||
if (npubComputed && npubComputed !== pub) {
|
||||
if (npubComputed !== props.id) {
|
||||
route(`/${npubComputed}`, true);
|
||||
return;
|
||||
}
|
||||
|
||||
const hexPubComputed = Key.toNostrHexAddress(pub) || '';
|
||||
setHexPub(pub.toHex());
|
||||
setNpub(npubComputed);
|
||||
} catch (e) {
|
||||
let nostrAddress = props.id;
|
||||
|
||||
if (hexPubComputed) {
|
||||
setHexPub(hexPubComputed);
|
||||
setNpub(Key.toNostrBech32Address(hexPubComputed, 'npub') || '');
|
||||
} else {
|
||||
let nostrAddress = pub;
|
||||
if (!nostrAddress.match(/.+@.+\..+/)) {
|
||||
if (nostrAddress.match(/.+\..+/)) {
|
||||
nostrAddress = '_@' + nostrAddress;
|
||||
@ -85,11 +85,8 @@ function Profile(props) {
|
||||
|
||||
Key.getPubKeyByNip05Address(nostrAddress).then((pubKey) => {
|
||||
if (pubKey) {
|
||||
const npubComputed = Key.toNostrBech32Address(pubKey, 'npub');
|
||||
if (npubComputed && npubComputed !== pubKey) {
|
||||
setNpub(npubComputed);
|
||||
setHexPub(pubKey);
|
||||
}
|
||||
setNpub(pubKey.toBech32());
|
||||
setHexPub(pubKey.toHex());
|
||||
} else {
|
||||
setNpub(''); // To indicate not found
|
||||
}
|
||||
@ -99,6 +96,7 @@ function Profile(props) {
|
||||
setTimeout(() => {
|
||||
window.prerenderReady = true;
|
||||
}, 1000);
|
||||
|
||||
return () => {
|
||||
setIsMyProfile(false);
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user