mirror of
https://github.com/BlowaterNostr/blowater.git
synced 2024-10-18 07:33:22 +00:00
add nip-07 back with proper error toast (#408)
This commit is contained in:
parent
df1a425910
commit
f1787eef9a
@ -11,44 +11,29 @@ import { LocalPrivateKeyController } from "./sign-in.ts";
|
||||
|
||||
type NIP07 = {
|
||||
getPublicKey(): Promise<string>;
|
||||
signEvent(event: UnsignedNostrEvent): Promise<NostrEvent>;
|
||||
signEvent<T extends NostrKind>(event: UnsignedNostrEvent<T>): Promise<NostrEvent<T>>;
|
||||
getRelays(): { [url: string]: { read: boolean; write: boolean } };
|
||||
nip04: {
|
||||
encrypt: (pubkey: string, plaintext: string) => Promise<string | Error>;
|
||||
decrypt: (pubkey: string, ciphertext: string) => Promise<string | Error>;
|
||||
};
|
||||
enabled: boolean;
|
||||
enable: () => Promise<{ enabled: boolean }>;
|
||||
nip44: {
|
||||
encrypt: (pubkey: string, plaintext: string) => Promise<string | Error>;
|
||||
decrypt: (pubkey: string, ciphertext: string) => Promise<string | Error>;
|
||||
};
|
||||
};
|
||||
|
||||
export class Nip7ExtensionContext implements NostrAccountContext {
|
||||
private readonly operationChan = chan<
|
||||
{
|
||||
op: "encrypt";
|
||||
pubkey: string;
|
||||
plaintext: string;
|
||||
} | {
|
||||
op: "signEvent";
|
||||
event: UnsignedNostrEvent;
|
||||
}
|
||||
>();
|
||||
|
||||
private readonly encryptChan = chan<string | Error>();
|
||||
// private readonly decryptChan = chan<string | Error>();
|
||||
private readonly signEventChan = chan<NostrEvent>();
|
||||
|
||||
static async New(): Promise<Nip7ExtensionContext | Error | undefined> {
|
||||
async function getExtensionObject(): Promise<NIP07 | undefined> {
|
||||
// wait for alby or nos2x init
|
||||
await sleep(20);
|
||||
if ("nostr" in window) {
|
||||
return window.nostr as NIP07;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
const ext = await getExtensionObject();
|
||||
if (ext === undefined) {
|
||||
// wait for nip-07 extension init
|
||||
await sleep(20);
|
||||
let ext;
|
||||
if ("nostr" in window) {
|
||||
ext = window.nostr as NIP07;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let pubkey: string | undefined;
|
||||
try {
|
||||
pubkey = await ext.getPublicKey();
|
||||
@ -63,53 +48,41 @@ export class Nip7ExtensionContext implements NostrAccountContext {
|
||||
}
|
||||
|
||||
private constructor(
|
||||
private alby: NIP07,
|
||||
private nip07: NIP07,
|
||||
public publicKey: PublicKey,
|
||||
) {
|
||||
console.log(alby);
|
||||
(async () => {
|
||||
for await (const op of this.operationChan) {
|
||||
if (op.op == "encrypt") {
|
||||
try {
|
||||
const res = await alby.nip04.encrypt(op.pubkey, op.plaintext);
|
||||
await this.encryptChan.put(res);
|
||||
} catch (e) {
|
||||
await this.encryptChan.put(e as Error);
|
||||
}
|
||||
} else if (op.op === "signEvent") {
|
||||
const res = await alby.signEvent(op.event);
|
||||
await this.signEventChan.put(res);
|
||||
}
|
||||
}
|
||||
})();
|
||||
console.log(nip07);
|
||||
}
|
||||
|
||||
async signEvent<T extends NostrKind = NostrKind>(event: UnsignedNostrEvent<T>): Promise<NostrEvent<T>> {
|
||||
await this.operationChan.put({ op: "signEvent", event: event });
|
||||
const res = await this.signEventChan.pop();
|
||||
if (res === closed) {
|
||||
throw new Error("unreachable");
|
||||
}
|
||||
// @ts-ignore
|
||||
return res;
|
||||
async signEvent<T extends NostrKind = NostrKind>(event: UnsignedNostrEvent<T>) {
|
||||
return this.nip07.signEvent(event);
|
||||
}
|
||||
|
||||
encrypt = async (pubkey: string, plaintext: string) => {
|
||||
await this.operationChan.put({
|
||||
op: "encrypt",
|
||||
pubkey,
|
||||
plaintext,
|
||||
});
|
||||
const res = await this.encryptChan.pop();
|
||||
if (res === closed) {
|
||||
throw new Error("unreachable");
|
||||
if (!("nip44" in this.nip07)) {
|
||||
return new Error(
|
||||
"This NIP-07 extension does not implement NIP-44, please use a NIP-44 compatible one",
|
||||
);
|
||||
}
|
||||
try {
|
||||
return this.nip07.nip44.encrypt(pubkey, plaintext);
|
||||
} catch (e) {
|
||||
return e as Error;
|
||||
}
|
||||
return res;
|
||||
};
|
||||
|
||||
decrypt = async (pubkey: string, ciphertext: string) => {
|
||||
try {
|
||||
const res = await this.alby.nip04.decrypt(pubkey, ciphertext);
|
||||
return res;
|
||||
if (ciphertext.includes("?iv")) {
|
||||
return await this.nip07.nip04.decrypt(pubkey, ciphertext);
|
||||
} else {
|
||||
if (!("nip44" in this.nip07)) {
|
||||
return new Error(
|
||||
"This NIP-07 extension does not implement NIP-44, please use a NIP-44 compatible one",
|
||||
);
|
||||
}
|
||||
return await this.nip07.nip44.decrypt(pubkey, ciphertext);
|
||||
}
|
||||
} catch (e) {
|
||||
return e as Error;
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
/** @jsx h */
|
||||
import { h, render, VNode } from "https://esm.sh/preact@10.17.1";
|
||||
import { Channel } from "https://raw.githubusercontent.com/BlowaterNostr/csp/master/csp.ts";
|
||||
import { PublicKey } from "../../libs/nostr.ts/key.ts";
|
||||
import { InvalidKey, PublicKey } from "../../libs/nostr.ts/key.ts";
|
||||
import { NostrAccountContext, NostrEvent, NostrKind } from "../../libs/nostr.ts/nostr.ts";
|
||||
import { ConnectionPool } from "../../libs/nostr.ts/relay-pool.ts";
|
||||
import { Database_View } from "../database.ts";
|
||||
@ -203,7 +203,9 @@ export class App {
|
||||
});
|
||||
if (error instanceof Error) {
|
||||
console.error(error.message);
|
||||
await args.database.remove(e.id);
|
||||
if (error instanceof InvalidKey) {
|
||||
await args.database.remove(e.id);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
continue;
|
||||
|
@ -14,6 +14,7 @@ import { SignInEvent } from "./sign-in.ts";
|
||||
import { SaveProfile } from "./edit-profile.tsx";
|
||||
import { setState } from "./_helper.ts";
|
||||
import { robohash } from "./relay-detail.tsx";
|
||||
import { Nip7ExtensionContext } from "./account-context.ts";
|
||||
|
||||
interface Props {
|
||||
emit: emitFunc<SaveProfile | SignInEvent>;
|
||||
@ -109,7 +110,24 @@ export class SignIn extends Component<Props, State> {
|
||||
onClick={() => this.setState({ step: { type: "signin", private_key: "" } })}
|
||||
class={`w-full p-3 rounded-lg flex items-center justify-center bg-[#4d4f57] hover:bg-[#6c6f77]`}
|
||||
>
|
||||
Login
|
||||
Login with private key
|
||||
</button>
|
||||
<div class="my-1"></div>
|
||||
<button
|
||||
onClick={async () => {
|
||||
const nip07 = await Nip7ExtensionContext.New();
|
||||
if (nip07 instanceof Error || nip07 == undefined) {
|
||||
console.error(nip07);
|
||||
return;
|
||||
}
|
||||
this.props.emit({
|
||||
type: "SignInEvent",
|
||||
ctx: nip07,
|
||||
});
|
||||
}}
|
||||
class={`w-full p-3 rounded-lg flex items-center justify-center bg-[#4d4f57] hover:bg-[#6c6f77]`}
|
||||
>
|
||||
Login with NIP-07 extensions
|
||||
</button>
|
||||
</Fragment>
|
||||
);
|
||||
|
@ -1,10 +1,18 @@
|
||||
import { prepareEncryptedNostrEvent } from "../../libs/nostr.ts/event.ts";
|
||||
import { PublicKey } from "../../libs/nostr.ts/key.ts";
|
||||
import { InvalidKey, PublicKey } from "../../libs/nostr.ts/key.ts";
|
||||
import { NostrAccountContext, NostrEvent, NostrKind } from "../../libs/nostr.ts/nostr.ts";
|
||||
import { ConnectionPool } from "../../libs/nostr.ts/relay-pool.ts";
|
||||
import { DirectMessageGetter } from "../UI/app_update.tsx";
|
||||
import { ChatMessage, parseContent } from "../UI/message.ts";
|
||||
import { compare, getTags, Parsed_Event, prepareNostrImageEvent, Tag, Tags } from "../nostr.ts";
|
||||
import {
|
||||
compare,
|
||||
DirectedMessage_Event,
|
||||
getTags,
|
||||
Parsed_Event,
|
||||
prepareNostrImageEvent,
|
||||
Tag,
|
||||
Tags,
|
||||
} from "../nostr.ts";
|
||||
import { EventSender } from "../../libs/nostr.ts/relay.interface.ts";
|
||||
|
||||
import {
|
||||
@ -203,7 +211,7 @@ export class DirectedMessageController implements DirectMessageGetter {
|
||||
} else {
|
||||
parsedTags = getTags(event);
|
||||
}
|
||||
let publicKey;
|
||||
let publicKey: PublicKey | InvalidKey;
|
||||
if ("publicKey" in event) {
|
||||
publicKey = event.publicKey;
|
||||
} else {
|
||||
@ -212,7 +220,7 @@ export class DirectedMessageController implements DirectMessageGetter {
|
||||
return publicKey;
|
||||
}
|
||||
}
|
||||
let dmEvent = await parseDM(
|
||||
const result = await parseDM(
|
||||
{
|
||||
...event,
|
||||
kind,
|
||||
@ -221,40 +229,43 @@ export class DirectedMessageController implements DirectMessageGetter {
|
||||
parsedTags,
|
||||
publicKey,
|
||||
);
|
||||
if ("type" in dmEvent) {
|
||||
if (dmEvent.type == "Other") {
|
||||
return dmEvent.error;
|
||||
} else if (dmEvent.type == "NotMyMessage") {
|
||||
return; // ignore
|
||||
}
|
||||
|
||||
if (result.type == "Other") {
|
||||
return result;
|
||||
} else if (result.type == "NotMyMessage") {
|
||||
return result;
|
||||
} else if (result.type == "error") {
|
||||
return result;
|
||||
}
|
||||
if (dmEvent instanceof Error) {
|
||||
return dmEvent;
|
||||
}
|
||||
const isImage = dmEvent.parsedTags.image;
|
||||
|
||||
const dm_event = result.event;
|
||||
const isImage = dm_event.parsedTags.image;
|
||||
let chatMessage: ChatMessage;
|
||||
if (isImage) {
|
||||
const imageBase64 = dmEvent.decryptedContent;
|
||||
const imageBase64 = dm_event.decryptedContent;
|
||||
chatMessage = {
|
||||
event: dmEvent,
|
||||
author: dmEvent.publicKey,
|
||||
event: dm_event,
|
||||
author: dm_event.publicKey,
|
||||
content: imageBase64,
|
||||
type: "image",
|
||||
created_at: new Date(dmEvent.created_at * 1000),
|
||||
lamport: dmEvent.parsedTags.lamport_timestamp,
|
||||
created_at: new Date(dm_event.created_at * 1000),
|
||||
lamport: dm_event.parsedTags.lamport_timestamp,
|
||||
};
|
||||
} else {
|
||||
chatMessage = {
|
||||
event: dmEvent,
|
||||
author: dmEvent.publicKey,
|
||||
content: dmEvent.decryptedContent,
|
||||
event: dm_event,
|
||||
author: dm_event.publicKey,
|
||||
content: dm_event.decryptedContent,
|
||||
type: "text",
|
||||
created_at: new Date(dmEvent.created_at * 1000),
|
||||
lamport: dmEvent.parsedTags.lamport_timestamp,
|
||||
created_at: new Date(dm_event.created_at * 1000),
|
||||
lamport: dm_event.parsedTags.lamport_timestamp,
|
||||
};
|
||||
}
|
||||
this.directed_messages.set(event.id, chatMessage);
|
||||
/* do not await */ this.new_message_chan.put(chatMessage);
|
||||
return {
|
||||
type: true,
|
||||
};
|
||||
}
|
||||
|
||||
onChange() {
|
||||
@ -277,22 +288,42 @@ async function parseDM(
|
||||
ctx: NostrAccountContext,
|
||||
parsedTags: Tags,
|
||||
publicKey: PublicKey,
|
||||
) {
|
||||
): Promise<
|
||||
{
|
||||
type: "NotMyMessage";
|
||||
error: Error;
|
||||
} | {
|
||||
type: "Other";
|
||||
error: Error;
|
||||
} | {
|
||||
type: "error";
|
||||
error: Error;
|
||||
} | {
|
||||
type: "event";
|
||||
event: DirectedMessage_Event;
|
||||
}
|
||||
> {
|
||||
const theOther = whoIamTalkingTo(event, ctx.publicKey);
|
||||
if (theOther.type != true) {
|
||||
return theOther;
|
||||
}
|
||||
const decrypted = await ctx.decrypt(theOther.talkingTo, event.content);
|
||||
if (decrypted instanceof Error) {
|
||||
return decrypted;
|
||||
return {
|
||||
type: "error",
|
||||
error: decrypted,
|
||||
};
|
||||
}
|
||||
return {
|
||||
...event,
|
||||
kind: event.kind,
|
||||
parsedTags,
|
||||
publicKey,
|
||||
decryptedContent: decrypted,
|
||||
parsedContentItems: Array.from(parseContent(decrypted)),
|
||||
type: "event",
|
||||
event: {
|
||||
...event,
|
||||
kind: event.kind,
|
||||
parsedTags,
|
||||
publicKey,
|
||||
decryptedContent: decrypted,
|
||||
parsedContentItems: Array.from(parseContent(decrypted)),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user