move more stuff out to applesauce

This commit is contained in:
hzrd149 2024-09-30 18:54:34 -05:00
parent aa2f2104f0
commit 38bc52b4c9
25 changed files with 82 additions and 560 deletions

View File

@ -42,7 +42,9 @@
"@uiw/codemirror-theme-github": "^4.23.0",
"@uiw/react-codemirror": "^4.23.0",
"@webscopeio/react-textarea-autocomplete": "^4.9.2",
"applesauce-core": "^0.4.0",
"applesauce-channel": "^0.5.0",
"applesauce-core": "^0.5.0",
"applesauce-signer": "^0.5.0",
"bech32": "^2.0.0",
"blossom-client-sdk": "^0.7.0",
"blossom-drive-sdk": "^0.4.0",

52
pnpm-lock.yaml generated
View File

@ -90,9 +90,15 @@ importers:
'@webscopeio/react-textarea-autocomplete':
specifier: ^4.9.2
version: 4.9.2(prop-types@15.8.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
applesauce-channel:
specifier: ^0.5.0
version: 0.5.0(typescript@5.6.2)
applesauce-core:
specifier: ^0.4.0
version: 0.4.0(typescript@5.6.2)
specifier: ^0.5.0
version: 0.5.0(typescript@5.6.2)
applesauce-signer:
specifier: ^0.5.0
version: 0.5.0(typescript@5.6.2)
bech32:
specifier: ^2.0.0
version: 2.0.0
@ -2182,9 +2188,6 @@ packages:
'@types/zen-observable@0.8.7':
resolution: {integrity: sha512-LKzNTjj+2j09wAo/vvVjzgw5qckJJzhdGgWHW7j69QIGdq/KnZrMAMIHQiWGl3Ccflh5/CudBAntTPYdprPltA==}
'@types/zen-push@0.1.4':
resolution: {integrity: sha512-FB0u2p9qwYRItXTio+Jwn+usx4zWIgdDXwMcSiZtjEFru/mIfJb9SedruzbBvEGHvq77sGt2e5cursvheyYcRQ==}
'@uiw/codemirror-extensions-basic-setup@4.23.3':
resolution: {integrity: sha512-nEMjgbCyeLx+UQgOGAAoUWYFE34z5TlyaKNszuig/BddYFDb0WKcgmC37bDFxR2dZssf3K/lwGWLpXnGKXePbA==}
peerDependencies:
@ -2280,8 +2283,14 @@ packages:
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
engines: {node: '>=8'}
applesauce-core@0.4.0:
resolution: {integrity: sha512-UvzmM+mh9D5g697NHJv6x9bzkXk64uaiFLiIJR5Yb+FpYYOsIRJZGT73fVmgVC8EAWkItZ9jYS25IUi4a0cgrg==}
applesauce-channel@0.5.0:
resolution: {integrity: sha512-0B4ldPKzO7RFfWpOyQnqf1JIAk2JKTx88atSGPtbZGYWM9kfG2FzBIEaaamKbQ12dfZqNzZRWvCFMww+5jX1Eg==}
applesauce-core@0.5.0:
resolution: {integrity: sha512-PqjiTsxBWnrlyBe9mP5KNJs9L7AQjrqWJoj/F52IqxdYgGjli3FubfV/lwjq+UQzwK22CMiwPoxTG3jLDlfpaw==}
applesauce-signer@0.5.0:
resolution: {integrity: sha512-MF1I0KQ0nm3vq/Z/lXL0279KqTJcu0BctvKSORNUBeV0GTlDsDdvrsYyKmsjJVVJunr8xhuOrsPvAew5dT54Lw==}
argparse@1.0.10:
resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==}
@ -7101,10 +7110,6 @@ snapshots:
'@types/zen-observable@0.8.7': {}
'@types/zen-push@0.1.4':
dependencies:
'@types/zen-observable': 0.8.7
'@uiw/codemirror-extensions-basic-setup@4.23.3(@codemirror/autocomplete@6.18.1(@codemirror/language@6.10.3)(@codemirror/state@6.4.1)(@codemirror/view@6.34.1)(@lezer/common@1.2.1))(@codemirror/commands@6.6.2)(@codemirror/language@6.10.3)(@codemirror/lint@6.8.2)(@codemirror/search@6.5.6)(@codemirror/state@6.4.1)(@codemirror/view@6.34.1)':
dependencies:
'@codemirror/autocomplete': 6.18.1(@codemirror/language@6.10.3)(@codemirror/state@6.4.1)(@codemirror/view@6.34.1)(@lezer/common@1.2.1)
@ -7209,18 +7214,39 @@ snapshots:
dependencies:
color-convert: 2.0.1
applesauce-core@0.4.0(typescript@5.6.2):
applesauce-channel@0.5.0(typescript@5.6.2):
dependencies:
applesauce-core: 0.5.0(typescript@5.6.2)
nostr-tools: 2.7.2(typescript@5.6.2)
transitivePeerDependencies:
- supports-color
- typescript
applesauce-core@0.5.0(typescript@5.6.2):
dependencies:
'@types/zen-push': 0.1.4
debug: 4.3.7
json-stringify-deterministic: 1.0.12
nanoid: 5.0.7
nostr-tools: 2.7.2(typescript@5.6.2)
zen-observable: 0.10.0
zen-push: 0.3.1
transitivePeerDependencies:
- supports-color
- typescript
applesauce-signer@0.5.0(typescript@5.6.2):
dependencies:
'@noble/hashes': 1.5.0
'@noble/secp256k1': 1.7.1
'@scure/base': 1.1.9
'@types/dom-serial': 1.0.6
applesauce-core: 0.5.0(typescript@5.6.2)
debug: 4.3.7
nostr-tools: 2.7.2(typescript@5.6.2)
transitivePeerDependencies:
- supports-color
- typescript
argparse@1.0.10:
dependencies:
sprintf-js: 1.0.3

View File

@ -1,16 +1,16 @@
import { AppSettings } from "../../services/settings/migrations";
import { Nip07Signer } from "../../types/nostr-extensions";
import { Nip07Interface } from "applesauce-signer";
export class Account {
readonly type: string = "unknown";
pubkey: string;
localSettings?: AppSettings;
protected _signer?: Nip07Signer | undefined;
public get signer(): Nip07Signer | undefined {
protected _signer?: Nip07Interface | undefined;
public get signer(): Nip07Interface | undefined {
return this._signer;
}
public set signer(value: Nip07Signer | undefined) {
public set signer(value: Nip07Interface | undefined) {
this._signer = value;
}

View File

@ -1,19 +1,19 @@
import AmberSigner from "../signers/amber-signer";
import { AmberClipboardSigner } from "applesauce-signer";
import { Account } from "./account";
export default class AmberAccount extends Account {
readonly type = "amber";
protected declare _signer?: AmberSigner | undefined;
public get signer(): AmberSigner | undefined {
protected declare _signer?: AmberClipboardSigner | undefined;
public get signer(): AmberClipboardSigner | undefined {
return this._signer;
}
public set signer(value: AmberSigner | undefined) {
public set signer(value: AmberClipboardSigner | undefined) {
this._signer = value;
}
constructor(pubkey: string) {
super(pubkey);
this.signer = new AmberSigner();
this.signer = new AmberClipboardSigner();
}
}

View File

@ -1,13 +1,13 @@
import { Nip07Signer } from "../../types/nostr-extensions";
import { Nip07Interface } from "applesauce-signer";
import { Account } from "./account";
export default class ExtensionAccount extends Account {
readonly type = "extension";
public get signer(): Nip07Signer | undefined {
public get signer(): Nip07Interface | undefined {
return window.nostr;
}
set signer(signer: Nip07Signer) {
set signer(signer: Nip07Interface) {
throw new Error("Cant update signer");
}

View File

@ -1,5 +1,5 @@
import { generateSecretKey, getPublicKey, nip19 } from "nostr-tools";
import SimpleSigner from "../signers/simple-signer";
import { SimpleSigner } from "applesauce-signer";
import { Account } from "./account";
export default class NsecAccount extends Account {

View File

@ -1,4 +1,4 @@
import SerialPortSigner from "../signers/serial-port-signer";
import { SerialPortSigner } from "applesauce-signer";
import { Account } from "./account";
export default class SerialPortAccount extends Account {

View File

@ -1,167 +0,0 @@
import { EventTemplate, NostrEvent, VerifiedEvent, getEventHash, nip19, verifyEvent } from "nostr-tools";
import createDefer, { Deferred } from "../deferred";
import { getPubkeyFromDecodeResult, isHex, isHexKey } from "../../helpers/nip19";
import { Nip07Signer } from "../../types/nostr-extensions";
export default class AmberSigner implements Nip07Signer {
static SUPPORTED = navigator.userAgent.includes("Android") && navigator.clipboard && navigator.clipboard.readText;
private pendingRequest: Deferred<string> | null = null;
public pubkey?: string;
verifyEvent: typeof verifyEvent = verifyEvent;
nip04?:
| {
encrypt: (pubkey: string, plaintext: string) => Promise<string> | string;
decrypt: (pubkey: string, ciphertext: string) => Promise<string> | string;
}
| undefined;
nip44?:
| {
encrypt: (pubkey: string, plaintext: string) => Promise<string> | string;
decrypt: (pubkey: string, ciphertext: string) => Promise<string> | string;
}
| undefined;
constructor() {
document.addEventListener("visibilitychange", this.onVisibilityChange);
this.nip04 = {
encrypt: this.nip04Encrypt.bind(this),
decrypt: this.nip04Decrypt.bind(this),
};
this.nip44 = {
encrypt: this.nip44Encrypt.bind(this),
decrypt: this.nip44Decrypt.bind(this),
};
}
private onVisibilityChange = () => {
if (document.visibilityState === "visible") {
if (!this.pendingRequest || !navigator.clipboard) return;
// read the result from the clipboard
setTimeout(() => {
navigator.clipboard
.readText()
.then((result) => this.pendingRequest?.resolve(result))
.catch((e) => this.pendingRequest?.reject(e));
}, 200);
}
};
private async intentRequest(intent: string) {
this.rejectPending();
const request = createDefer<string>();
window.open(intent, "_blank");
// NOTE: wait 500ms before setting the pending request since the visibilitychange event fires as soon as window.open is called
setTimeout(() => {
this.pendingRequest = request;
}, 500);
const result = await request;
if (result.length === 0) throw new Error("Empty clipboard");
return result;
}
rejectPending() {
if (this.pendingRequest) {
this.pendingRequest.reject("Canceled");
this.pendingRequest = null;
}
}
public destroy() {
document.removeEventListener("visibilitychange", this.onVisibilityChange);
}
private checkSupport() {
if (!AmberSigner.SUPPORTED) throw new Error("Cant use Amber on non-Android device");
}
public async getPublicKey() {
this.checkSupport();
if (this.pubkey) return this.pubkey;
const result = await this.intentRequest(AmberSigner.createGetPublicKeyIntent());
if (isHexKey(result)) {
this.pubkey = result;
return result;
} else if (result.startsWith("npub") || result.startsWith("nprofile")) {
const decode = nip19.decode(result);
const pubkey = getPubkeyFromDecodeResult(decode);
if (!pubkey) throw new Error("Expected npub from clipboard");
this.pubkey = pubkey;
return pubkey;
}
throw new Error("Expected clipboard to have pubkey");
}
public async signEvent(draft: EventTemplate & { pubkey?: string }): Promise<VerifiedEvent> {
this.checkSupport();
const pubkey = draft.pubkey || this.pubkey;
if (!pubkey) throw new Error("Unknown signer pubkey");
const draftWithId = { ...draft, id: getEventHash({ ...draft, pubkey }) };
const sig = await this.intentRequest(AmberSigner.createSignEventIntent(draftWithId));
if (!isHex(sig)) throw new Error("Expected hex signature");
const event: NostrEvent = { ...draftWithId, sig, pubkey };
if (!this.verifyEvent(event)) throw new Error("Invalid signature");
return event;
}
// NIP-04
public async nip04Encrypt(pubkey: string, plaintext: string): Promise<string> {
this.checkSupport();
const data = await this.intentRequest(AmberSigner.createNip04EncryptIntent(pubkey, plaintext));
return data;
}
public async nip04Decrypt(pubkey: string, data: string): Promise<string> {
this.checkSupport();
const plaintext = await this.intentRequest(AmberSigner.createNip04DecryptIntent(pubkey, data));
return plaintext;
}
// NIP-44
public async nip44Encrypt(pubkey: string, plaintext: string): Promise<string> {
this.checkSupport();
const data = await this.intentRequest(AmberSigner.createNip44EncryptIntent(pubkey, plaintext));
return data;
}
public async nip44Decrypt(pubkey: string, data: string): Promise<string> {
this.checkSupport();
const plaintext = await this.intentRequest(AmberSigner.createNip44DecryptIntent(pubkey, data));
return plaintext;
}
// static methods
static createGetPublicKeyIntent() {
return `intent:#Intent;scheme=nostrsigner;S.compressionType=none;S.returnType=signature;S.type=get_public_key;end`;
}
static createSignEventIntent(draft: EventTemplate) {
return `intent:${encodeURIComponent(
JSON.stringify(draft),
)}#Intent;scheme=nostrsigner;S.compressionType=none;S.returnType=signature;S.type=sign_event;end`;
}
static createNip04EncryptIntent(pubkey: string, plainText: string) {
return `intent:${encodeURIComponent(
plainText,
)}#Intent;scheme=nostrsigner;S.pubKey=${pubkey};S.compressionType=none;S.returnType=signature;S.type=nip04_encrypt;end`;
}
static createNip04DecryptIntent(pubkey: string, ciphertext: string) {
return `intent:${encodeURIComponent(
ciphertext,
)}#Intent;scheme=nostrsigner;S.pubKey=${pubkey};S.compressionType=none;S.returnType=signature;S.type=nip04_decrypt;end`;
}
static createNip44EncryptIntent(pubkey: string, plainText: string) {
return `intent:${encodeURIComponent(
plainText,
)}#Intent;scheme=nostrsigner;S.pubKey=${pubkey};S.compressionType=none;S.returnType=signature;S.type=nip44_encrypt;end`;
}
static createNip44DecryptIntent(pubkey: string, ciphertext: string) {
return `intent:${encodeURIComponent(
ciphertext,
)}#Intent;scheme=nostrsigner;S.pubKey=${pubkey};S.compressionType=none;S.returnType=signature;S.type=nip44_decrypt;end`;
}
}

View File

@ -11,11 +11,11 @@ import {
import dayjs from "dayjs";
import { nanoid } from "nanoid";
import { bytesToHex, hexToBytes } from "@noble/hashes/utils";
import { Nip07Interface } from "applesauce-signer";
import MultiSubscription from "../multi-subscription";
import { logger } from "../../helpers/debug";
import createDefer, { Deferred } from "../deferred";
import { Nip07Signer } from "../../types/nostr-extensions";
export function isErrorResponse(response: any): response is NostrConnectErrorResponse {
return !!response.error;
@ -66,7 +66,7 @@ export type NostrConnectErrorResponse = {
error: string;
};
export default class NostrConnectSigner implements Nip07Signer {
export default class NostrConnectSigner implements Nip07Interface {
sub: MultiSubscription;
log = logger.extend("NostrConnectSigner");

View File

@ -1,8 +1,8 @@
import { EventTemplate, finalizeEvent, getPublicKey, nip04, nip44 } from "nostr-tools";
import { bytesToHex, hexToBytes } from "@noble/hashes/utils";
import { encrypt, decrypt } from "nostr-tools/nip49";
import { Nip07Interface } from "applesauce-signer";
import { Nip07Signer } from "../../types/nostr-extensions";
import createDefer, { Deferred } from "../deferred";
import db from "../../services/db";
@ -75,7 +75,7 @@ async function subltCryptoDecryptSecKey(buffer: ArrayBuffer, iv: Uint8Array, pas
}
}
export default class PasswordSigner implements Nip07Signer {
export default class PasswordSigner implements Nip07Interface {
key: Uint8Array | null = null;
// legacy

View File

@ -1,263 +0,0 @@
import { EventTemplate, getEventHash, NostrEvent, verifyEvent } from "nostr-tools";
import { base64 } from "@scure/base";
import { randomBytes, hexToBytes } from "@noble/hashes/utils";
import { Point } from "@noble/secp256k1";
import { logger } from "../../helpers/debug";
import { Nip07Signer } from "../../types/nostr-extensions";
import createDefer, { Deferred } from "../deferred";
type Callback = () => void;
type DeviceOpts = {
onConnect?: Callback;
onDisconnect?: Callback;
onError?: (err: Error) => void;
onDone?: Callback;
};
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
function xOnlyToXY(p: string) {
return Point.fromHex(p).toHex().substring(2);
}
export const utf8Decoder = new TextDecoder("utf-8");
export const utf8Encoder = new TextEncoder();
export default class SerialPortSigner implements Nip07Signer {
log = logger.extend("SerialSigner");
writer: WritableStreamDefaultWriter<string> | null = null;
pubkey?: string;
get isConnected() {
return !!this.writer;
}
verifyEvent: typeof verifyEvent = verifyEvent;
nip04?:
| {
encrypt: (pubkey: string, plaintext: string) => Promise<string> | string;
decrypt: (pubkey: string, ciphertext: string) => Promise<string> | string;
}
| undefined;
constructor() {
this.nip04 = {
encrypt: this.nip04Encrypt.bind(this),
decrypt: this.nip04Decrypt.bind(this),
};
}
lastCommand: Deferred<string> | null = null;
async callMethodOnDevice(method: string, params: string[], opts: DeviceOpts = {}) {
if (!SerialPortSigner.SUPPORTED) throw new Error("Serial devices are not supported");
if (!this.writer) await this.connectToDevice(opts);
// only one command can be pending at any time
// but each will only wait 6 seconds
if (this.lastCommand) throw new Error("Previous command to device still pending!");
const command = createDefer<string>();
this.lastCommand = command;
// send actual command
this.sendCommand(method, params);
setTimeout(() => {
command.reject(new Error("Device timeout"));
if (this.lastCommand === command) this.lastCommand = null;
}, 6000);
return this.lastCommand;
}
async connectToDevice({ onConnect, onDisconnect, onError, onDone }: DeviceOpts): Promise<void> {
let port: SerialPort = await navigator.serial.requestPort();
let reader;
const startSerialPortReading = async () => {
// reading responses
while (port && port.readable) {
const textDecoder = new window.TextDecoderStream();
port.readable.pipeTo(textDecoder.writable);
reader = textDecoder.readable.getReader();
const readStringUntil = this.readFromSerialPort(reader);
try {
while (true) {
const { value, done } = await readStringUntil("\n");
if (value) {
const { method, data } = this.parseResponse(value);
// if (method === "/log") deviceLog(data);
if (method === "/ping") this.log("Pong");
if (SerialPortSigner.PUBLIC_METHODS.indexOf(method) === -1) {
// ignore /ping, /log responses
continue;
}
this.log("Received: ", method, data);
if (this.lastCommand) {
this.lastCommand.resolve(data);
this.lastCommand = null;
}
}
if (done) {
this.lastCommand = null;
this.writer = null;
if (onDone) onDone();
return;
}
}
} catch (error) {
if (error instanceof Error) {
this.writer = null;
if (onError) onError(error);
if (this.lastCommand) {
this.lastCommand.reject(error);
this.lastCommand = null;
}
throw error;
}
}
}
};
await port.open({ baudRate: 9600 });
// this `sleep()` is a hack, I know!
// but `port.onconnect` is never called. I don't know why!
await sleep(1000);
startSerialPortReading();
const textEncoder = new window.TextEncoderStream();
textEncoder.readable.pipeTo(port.writable);
this.writer = textEncoder.writable.getWriter();
// send ping first
await this.sendCommand(SerialPortSigner.METHOD_PING);
await this.sendCommand(SerialPortSigner.METHOD_PING, [window.location.host]);
if (onConnect) onConnect();
port.addEventListener("disconnect", () => {
this.log("Disconnected");
this.lastCommand = null;
this.writer = null;
if (onDisconnect) onDisconnect();
});
}
async sendCommand(method: string, params: string[] = []) {
if (!this.writer) return;
this.log("Send command", method, params);
const message = [method].concat(params).join(" ");
await this.writer.write(message + "\n");
}
private readFromSerialPort(reader: ReadableStreamDefaultReader<string>) {
let partialChunk: string | undefined;
let fulliness: string[] = [];
const readStringUntil = async (separator = "\n") => {
if (fulliness.length) return { value: fulliness.shift()!.trim(), done: false };
const chunks = [];
if (partialChunk) {
// leftovers from previous read
chunks.push(partialChunk);
partialChunk = undefined;
}
while (true) {
const { value, done } = await reader.read();
if (value) {
const values = value.split(separator);
// found one or more separators
if (values.length > 1) {
chunks.push(values.shift()); // first element
partialChunk = values.pop(); // last element
fulliness = values; // full lines
return { value: chunks.join("").trim(), done: false };
}
chunks.push(value);
}
if (done) return { value: chunks.join("").trim(), done: true };
}
};
return readStringUntil;
}
private parseResponse(value: string) {
const method = value.split(" ")[0];
const data = value.substring(method.length).trim();
return { method, data };
}
// NIP-04
public async nip04Encrypt(pubkey: string, text: string) {
const sharedSecretStr = await this.callMethodOnDevice(SerialPortSigner.METHOD_SHARED_SECRET, [xOnlyToXY(pubkey)]);
const sharedSecret = hexToBytes(sharedSecretStr);
let iv = Uint8Array.from(randomBytes(16));
let plaintext = utf8Encoder.encode(text);
let cryptoKey = await crypto.subtle.importKey("raw", sharedSecret, { name: "AES-CBC" }, false, ["encrypt"]);
let ciphertext = await crypto.subtle.encrypt({ name: "AES-CBC", iv }, cryptoKey, plaintext);
let ctb64 = base64.encode(new Uint8Array(ciphertext));
let ivb64 = base64.encode(new Uint8Array(iv.buffer));
return `${ctb64}?iv=${ivb64}`;
}
public async nip04Decrypt(pubkey: string, data: string) {
let [ctb64, ivb64] = data.split("?iv=");
const sharedSecretStr = await this.callMethodOnDevice(SerialPortSigner.METHOD_SHARED_SECRET, [xOnlyToXY(pubkey)]);
const sharedSecret = hexToBytes(sharedSecretStr);
let cryptoKey = await crypto.subtle.importKey("raw", sharedSecret, { name: "AES-CBC" }, false, ["decrypt"]);
let ciphertext = base64.decode(ctb64);
let iv = base64.decode(ivb64);
let plaintext = await crypto.subtle.decrypt({ name: "AES-CBC", iv }, cryptoKey, ciphertext);
let text = utf8Decoder.decode(plaintext);
return text;
}
public async getPublicKey() {
const pubkey = await this.callMethodOnDevice(SerialPortSigner.METHOD_PUBLIC_KEY, []);
this.pubkey = pubkey;
return pubkey;
}
public async signEvent(draft: EventTemplate & { pubkey?: string }) {
const pubkey = draft.pubkey || this.pubkey;
if (!pubkey) throw new Error("Unknown signer pubkey");
const draftWithId = { ...draft, id: getEventHash({ ...draft, pubkey }) };
const sig = await this.callMethodOnDevice(SerialPortSigner.METHOD_SIGN_MESSAGE, [draftWithId.id]);
const event: NostrEvent = { ...draftWithId, sig, pubkey };
if (!this.verifyEvent(event)) throw new Error("Invalid signature");
return event;
}
public ping() {
this.sendCommand(SerialPortSigner.METHOD_PING, [window.location.host]);
}
// static const
static SUPPORTED = !!navigator.serial;
static METHOD_PING = "/ping";
static METHOD_LOG = "/log";
static METHOD_SIGN_MESSAGE = "/sign-message";
static METHOD_SHARED_SECRET = "/shared-secret";
static METHOD_PUBLIC_KEY = "/public-key";
static PUBLIC_METHODS = [
SerialPortSigner.METHOD_PUBLIC_KEY,
SerialPortSigner.METHOD_SIGN_MESSAGE,
SerialPortSigner.METHOD_SHARED_SECRET,
];
}

View File

@ -1,31 +0,0 @@
import { EventTemplate, finalizeEvent, generateSecretKey, getPublicKey, nip04, nip44 } from "nostr-tools";
export default class SimpleSigner {
key: Uint8Array;
constructor(key?: Uint8Array) {
this.key = key || generateSecretKey();
}
async getPublicKey() {
return getPublicKey(this.key);
}
async signEvent(event: EventTemplate) {
return finalizeEvent(event, this.key);
}
nip04 = {
encrypt: async (pubkey: string, plaintext: string) => nip04.encrypt(this.key, pubkey, plaintext),
decrypt: async (pubkey: string, ciphertext: string) => nip04.decrypt(this.key, pubkey, ciphertext),
};
nip44 = {
encrypt: async (pubkey: string, plaintext: string) =>
nip44.v2.encrypt(plaintext, nip44.v2.utils.getConversationKey(this.key, pubkey)),
decrypt: async (pubkey: string, ciphertext: string) =>
nip44.v2.decrypt(ciphertext, nip44.v2.utils.getConversationKey(this.key, pubkey)),
};
}
if (import.meta.env.DEV) {
// @ts-expect-error
window.SimpleSigner = SimpleSigner;
}

View File

@ -2,12 +2,12 @@ import { SubCloser } from "nostr-tools/abstract-pool";
import EventEmitter from "eventemitter3";
import { generateSecretKey, nip19, NostrEvent } from "nostr-tools";
import { bytesToHex, hexToBytes } from "@noble/hashes/utils";
import { SimpleSigner } from "applesauce-signer";
import dayjs from "dayjs";
import NostrWebRTCPeer, { Pool, RTCDescriptionEventKind, Signer } from "./nostr-webrtc-peer";
import { isHex } from "../../helpers/nip19";
import { logger } from "../../helpers/debug";
import SimpleSigner from "../signers/simple-signer";
type EventMap = {
call: [NostrEvent];

View File

@ -1,8 +1,6 @@
import { getPublicKey, nip19 } from "nostr-tools";
import { Tag, isATag, isETag, isPTag } from "../types/nostr-event";
import { safeRelayUrls } from "./relay";
import { parseCoordinate } from "./nostr/event";
export function isHex(str?: string) {
if (str?.match(/^[0-9a-f]+$/i)) return true;

View File

@ -1,5 +1,5 @@
import { useMemo } from "react";
import { Queries } from "applesauce-core";
import { ChannelMetadataQuery } from "applesauce-channel";
import { RequestOptions } from "../services/replaceable-events";
import channelMetadataService from "../services/channel-metadata";
@ -17,7 +17,7 @@ export default function useChannelMetadata(
return channelMetadataService.requestMetadata(relays, channelId, opts);
}, [channelId, Array.from(relays).join("|"), opts?.alwaysRequest, opts?.ignoreCache]);
const metadata = useStoreQuery(Queries.ChannelMetadataQuery, channel && [channel]);
const metadata = useStoreQuery(ChannelMetadataQuery, channel && [channel]);
return metadata;
}

View File

@ -1,13 +1,8 @@
import { AmberClipboardSigner } from "applesauce-signer";
import { alwaysVerify } from "./verify-event";
import AmberSigner from "../classes/signers/amber-signer";
/** @deprecated nip NostrConnectClient instead */
const amberSignerService = new AmberSigner();
/** @deprecated use AmberClipboardSigner class instead */
const amberSignerService = new AmberClipboardSigner();
amberSignerService.verifyEvent = alwaysVerify;
if (import.meta.env.DEV) {
// @ts-ignore
window.amberSignerService = amberSignerService;
}
export default amberSignerService;

View File

@ -1,7 +1,7 @@
import SerialPortSigner from "../classes/signers/serial-port-signer";
import { SerialPortSigner } from "applesauce-signer";
import { alwaysVerify } from "./verify-event";
/** @deprecated */
/** @deprecated use SerialPortSigner class instead */
const serialPortService = new SerialPortSigner();
serialPortService.verifyEvent = alwaysVerify;
@ -11,9 +11,4 @@ setInterval(() => {
}
}, 1000 * 10);
if (import.meta.env.DEV) {
//@ts-ignore
window.serialPortService = serialPortService;
}
export default serialPortService;

View File

@ -1,5 +1,6 @@
import { NostrEvent, SimplePool } from "nostr-tools";
import { AbstractRelay } from "nostr-tools/abstract-relay";
import { SimpleSigner } from "applesauce-signer";
import { logger } from "../helpers/debug";
import NostrWebRtcBroker from "../classes/webrtc/nostr-webrtc-broker";
@ -7,7 +8,6 @@ import WebRtcRelayClient from "../classes/webrtc/webrtc-relay-client";
import WebRtcRelayServer from "../classes/webrtc/webrtc-relay-server";
import NostrWebRTCPeer from "../classes/webrtc/nostr-webrtc-peer";
import verifyEventMethod from "./verify-event";
import SimpleSigner from "../classes/signers/simple-signer";
import { localRelay } from "./local-relay";
import localSettings from "./local-settings";
import { DEFAULT_ICE_SERVERS } from "../const";

View File

@ -1,21 +1,8 @@
import { EventTemplate, NostrEvent, UnsignedEvent, VerifiedEvent } from "nostr-tools";
export type Nip07Signer = {
getPublicKey: () => Promise<string> | string;
signEvent: (template: EventTemplate) => Promise<VerifiedEvent> | VerifiedEvent;
getRelays?: () => Record<string, { read: boolean; write: boolean }> | string[];
nip04?: {
encrypt: (pubkey: string, plaintext: string) => Promise<string> | string;
decrypt: (pubkey: string, ciphertext: string) => Promise<string> | string;
};
nip44?: {
encrypt: (pubkey: string, plaintext: string) => Promise<string> | string;
decrypt: (pubkey: string, ciphertext: string) => Promise<string> | string;
};
};
import { Nip07Interface } from "applesauce-signer";
declare global {
interface Window {
nostr?: Nip07Signer;
nostr?: Nip07Interface;
}
}

View File

@ -1 +0,0 @@
import "@types/dom-serial";

View File

@ -2,7 +2,7 @@ import { memo, useCallback, useMemo } from "react";
import { useNavigate } from "react-router-dom";
import { Button, Flex, Heading, Spacer, Spinner, useDisclosure } from "@chakra-ui/react";
import { kinds } from "nostr-tools";
import { Queries } from "applesauce-core/query-store";
import { ChannelHiddenQuery, ChannelMessagesQuery, ChannelMutedQuery } from "applesauce-channel";
import useSingleEvent from "../../hooks/use-single-event";
import { ErrorBoundary } from "../../components/error-boundary";
@ -28,9 +28,9 @@ import { truncateId } from "../../helpers/string";
import { useStoreQuery } from "../../hooks/use-store-query";
const ChannelChatLog = memo(({ timeline, channel }: { timeline: TimelineLoader; channel: NostrEvent }) => {
const messages = useStoreQuery(Queries.ChannelMessagesQuery, [channel]) ?? [];
const mutes = useStoreQuery(Queries.ChannelMutedQuery, [channel]);
const hidden = useStoreQuery(Queries.ChannelHiddenQuery, [channel]);
const messages = useStoreQuery(ChannelMessagesQuery, [channel]) ?? [];
const mutes = useStoreQuery(ChannelMutedQuery, [channel]);
const hidden = useStoreQuery(ChannelHiddenQuery, [channel]);
const filteredMessages = useMemo(
() =>

View File

@ -1,16 +1,6 @@
import {
Box,
Button,
ButtonGroup,
Divider,
Flex,
FormControl,
FormLabel,
Heading,
Input,
Text,
} from "@chakra-ui/react";
import { Link as RouterLink, useNavigate } from "react-router-dom";
import { Box, Button, ButtonGroup, Divider, Flex, Heading, Text } from "@chakra-ui/react";
import { useNavigate } from "react-router-dom";
import { SimpleSigner } from "applesauce-signer";
import VerticalPageLayout from "../../../components/vertical-page-layout";
import useCurrentAccount from "../../../hooks/use-current-account";
@ -21,7 +11,6 @@ import accountService from "../../../services/account";
import AccountTypeBadge from "../../../components/account-info-badge";
import useSubject from "../../../hooks/use-subject";
import PasswordSigner from "../../../classes/signers/password-signer";
import SimpleSigner from "../../../classes/signers/simple-signer";
import SimpleSignerBackup from "./simple-signer-backup";
import PasswordSignerBackup from "./password-signer-backup";

View File

@ -1,4 +1,3 @@
import { useCallback, useState } from "react";
import {
Button,
ButtonGroup,
@ -11,16 +10,13 @@ import {
useDisclosure,
useToast,
} from "@chakra-ui/react";
import { nip19 } from "nostr-tools";
import { encrypt } from "nostr-tools/nip49";
import { useForm } from "react-hook-form";
import SimpleSigner from "../../../classes/signers/simple-signer";
import useCurrentAccount from "../../../hooks/use-current-account";
import EyeOff from "../../../components/icons/eye-off";
import Eye from "../../../components/icons/eye";
import { CopyIconButton } from "../../../components/copy-icon-button";
import PasswordSigner from "../../../classes/signers/password-signer";
import { useForm } from "react-hook-form";
const fake = Array(48).fill("x");

View File

@ -1,10 +1,8 @@
import { useCallback, useState } from "react";
import {
Button,
ButtonGroup,
Flex,
FormControl,
FormHelperText,
FormLabel,
Heading,
IconButton,
@ -15,8 +13,8 @@ import {
import { nip19 } from "nostr-tools";
import { encrypt } from "nostr-tools/nip49";
import { useForm } from "react-hook-form";
import { SimpleSigner } from "applesauce-signer";
import SimpleSigner from "../../../classes/signers/simple-signer";
import useCurrentAccount from "../../../hooks/use-current-account";
import EyeOff from "../../../components/icons/eye-off";
import Eye from "../../../components/icons/eye";

View File

@ -1,21 +1,19 @@
import { useState } from "react";
import { Button, ButtonGroup, Divider, Flex, IconButton, Link, Spinner, Text, useToast } from "@chakra-ui/react";
import { Link as RouterLink, useLocation } from "react-router-dom";
import { AmberClipboardSigner, SerialPortSigner } from "applesauce-signer";
import Key01 from "../../components/icons/key-01";
import Diamond01 from "../../components/icons/diamond-01";
import UsbFlashDrive from "../../components/icons/usb-flash-drive";
import HelpCircle from "../../components/icons/help-circle";
import { COMMON_CONTACT_RELAY } from "../../const";
import accountService from "../../services/account";
import serialPortService from "../../services/serial-port";
import amberSignerService from "../../services/amber-signer";
import AmberSigner from "../../classes/signers/amber-signer";
import { AtIcon } from "../../components/icons";
import Package from "../../components/icons/package";
import Eye from "../../components/icons/eye";
import SerialPortSigner from "../../classes/signers/serial-port-signer";
import ExtensionAccount from "../../classes/accounts/extension-account";
import SerialPortAccount from "../../classes/accounts/serial-port-account";
import AmberAccount from "../../classes/accounts/amber-account";
@ -98,7 +96,7 @@ export default function LoginStartView() {
/>
</ButtonGroup>
)}
{AmberSigner.SUPPORTED && (
{AmberClipboardSigner.SUPPORTED && (
<ButtonGroup colorScheme="orange" w="full">
<Button onClick={signinWithAmber} leftIcon={<Diamond01 boxSize={6} />} flex={1}>
Use Amber