-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathinpage.js
More file actions
100 lines (87 loc) · 2.99 KB
/
Copy pathinpage.js
File metadata and controls
100 lines (87 loc) · 2.99 KB
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
// SPDX-FileCopyrightText: 2026 Foundation Devices, Inc. <hello@foundation.xyz>
// SPDX-License-Identifier: GPL-3.0-or-later
// Injected into every page. Defines `window.nostr` per NIP-07 and relays
// calls to the content script via window.postMessage.
//
// Wire protocol (both directions):
// { source: "prime-nostr-signer", id: <uuid>, kind: "request" | "response",
// method: <str>, params?: any, result?: any, error?: any }
//
// The content script runs in the ISOLATED world, listens for "request"
// messages on window, forwards to the service worker, and posts back a
// "response" with the matching id.
(function () {
"use strict";
const SOURCE = "prime-nostr-signer";
const pending = new Map();
function uuid() {
// Short random id is fine — only needs to be unique while in-flight.
const b = new Uint8Array(8);
crypto.getRandomValues(b);
return Array.from(b, (x) => x.toString(16).padStart(2, "0")).join("");
}
function call(method, params) {
return new Promise((resolve, reject) => {
const id = uuid();
pending.set(id, { resolve, reject });
window.postMessage(
{ source: SOURCE, id, kind: "request", method, params: params || {} },
window.location.origin,
);
// Safety: time out after 5 minutes so approval prompts don't leak.
setTimeout(() => {
if (pending.has(id)) {
pending.delete(id);
reject(new Error("signer request timed out"));
}
}, 5 * 60 * 1000);
});
}
window.addEventListener("message", (ev) => {
if (ev.source !== window) return;
const data = ev.data;
if (!data || data.source !== SOURCE || data.kind !== "response") return;
const entry = pending.get(data.id);
if (!entry) return;
pending.delete(data.id);
if (data.error) {
entry.reject(new Error(data.error.message || "signer error"));
} else {
entry.resolve(data.result);
}
});
const nip04 = {
encrypt: (pubkey, plaintext) =>
call("nip04_encrypt", { peer_pubkey: pubkey, plaintext }).then((r) => r.ciphertext),
decrypt: (pubkey, ciphertext) =>
call("nip04_decrypt", { peer_pubkey: pubkey, ciphertext }).then((r) => r.plaintext),
};
const nip44 = {
encrypt: (pubkey, plaintext) =>
call("nip44_encrypt", { peer_pubkey: pubkey, plaintext }).then((r) => r.ciphertext),
decrypt: (pubkey, ciphertext) =>
call("nip44_decrypt", { peer_pubkey: pubkey, ciphertext }).then((r) => r.plaintext),
};
window.nostr = {
async getPublicKey() {
const r = await call("get_public_key", {});
return r.pubkey;
},
async signEvent(event) {
return await call("sign_event", { event });
},
async getRelays() {
// Signer does not track relay preferences in v1.
return {};
},
nip04,
nip44,
};
// Let consumers feature-detect.
Object.defineProperty(window.nostr, "_provider", {
value: "passport-prime",
enumerable: false,
writable: false,
configurable: false,
});
})();