-
Notifications
You must be signed in to change notification settings - Fork 32
/
amber-signer.ts
107 lines (93 loc) · 3.74 KB
/
amber-signer.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
import { EventTemplate, NostrEvent, VerifiedEvent, getEventHash, nip19 } from "nostr-tools";
import createDefer, { Deferred } from "../classes/deferred";
import { getPubkeyFromDecodeResult, isHex, isHexKey } from "../helpers/nip19";
import { alwaysVerify } from "./verify-event";
export function createGetPublicKeyIntent() {
return `intent:#Intent;scheme=nostrsigner;S.compressionType=none;S.returnType=signature;S.type=get_public_key;end`;
}
export function createSignEventIntent(draft: EventTemplate) {
return `intent:${encodeURIComponent(
JSON.stringify(draft),
)}#Intent;scheme=nostrsigner;S.compressionType=none;S.returnType=signature;S.type=sign_event;end`;
}
export function 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`;
}
export function createNip04DecryptIntent(pubkey: string, data: string) {
return `intent:${encodeURIComponent(
data,
)}#Intent;scheme=nostrsigner;S.pubKey=${pubkey};S.compressionType=none;S.returnType=signature;S.type=nip04_decrypt;end`;
}
let pendingRequest: Deferred<string> | null = null;
function rejectPending() {
if (pendingRequest) {
pendingRequest.reject("Canceled");
pendingRequest = null;
}
}
function onVisibilityChange() {
if (document.visibilityState === "visible") {
if (!pendingRequest || !navigator.clipboard) return;
// read the result from the clipboard
setTimeout(() => {
navigator.clipboard
.readText()
.then((result) => pendingRequest?.resolve(result))
.catch((e) => pendingRequest?.reject(e));
}, 200);
}
}
document.addEventListener("visibilitychange", onVisibilityChange);
async function intentRequest(intent: string) {
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(() => {
pendingRequest = request;
}, 500);
const result = await request;
if (result.length === 0) throw new Error("Empty clipboard");
return result;
}
async function getPublicKey() {
const result = await intentRequest(createGetPublicKeyIntent());
if (isHexKey(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");
return pubkey;
}
throw new Error("Expected clipboard to have pubkey");
}
async function signEvent(draft: EventTemplate & { pubkey: string }): Promise<VerifiedEvent> {
const draftWithId = { ...draft, id: getEventHash(draft) };
const sig = await intentRequest(createSignEventIntent(draftWithId));
if (!isHex(sig)) throw new Error("Expected hex signature");
const event: NostrEvent = { ...draftWithId, sig };
if (!alwaysVerify(event)) throw new Error("Invalid signature");
return event;
}
async function nip04Encrypt(pubkey: string, plaintext: string): Promise<string> {
const data = await intentRequest(createNip04EncryptIntent(pubkey, plaintext));
return data;
}
async function nip04Decrypt(pubkey: string, data: string): Promise<string> {
const plaintext = await intentRequest(createNip04DecryptIntent(pubkey, data));
return plaintext;
}
const amberSignerService = {
supported: navigator.userAgent.includes("Android") && navigator.clipboard && navigator.clipboard.readText,
getPublicKey,
signEvent,
nip04Encrypt,
nip04Decrypt,
};
if (import.meta.env.DEV) {
// @ts-ignore
window.amberSignerService = amberSignerService;
}
export default amberSignerService;