aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdrien Hopkins <[email protected]>2025-10-18 19:12:28 -0500
committerAdrien Hopkins <[email protected]>2025-10-18 19:50:02 -0500
commit38a691cd6004adde24c913f69b2bbd01b2541fb9 (patch)
treee7125bdec8c03613502955af235bb3f6bd547ee7
parent1f95987dbfefd5c337b32f4b01700f2525f40883 (diff)
downloadcoinos-ui-38a691cd6004adde24c913f69b2bbd01b2541fb9.tar.gz
coinos-ui-38a691cd6004adde24c913f69b2bbd01b2541fb9.zip
Add DM expiration functionality
-rw-r--r--src/lib/nip17.ts23
-rw-r--r--src/routes/(app)/dm/[recipient]/+page.svelte23
2 files changed, 35 insertions, 11 deletions
diff --git a/src/lib/nip17.ts b/src/lib/nip17.ts
index 6cb8f9e3..9dd59f6f 100644
--- a/src/lib/nip17.ts
+++ b/src/lib/nip17.ts
@@ -10,20 +10,23 @@ const randomTimestamp = () => Math.floor(
// If wrapPK is specified and different than receiverPK,
// the message will be gift-wrapped to wrapPK
// but the rumour will be sent to receiverPK.
-export const createNIP17MessageSK = (text: string, senderSK: Uint8Array, receiverPK: string, wrapPK: string = null) => {
+export const createNIP17MessageSK = (text: string, senderSK: Uint8Array, receiverPK: string, wrapPK: string = null, expiryDays: number = null) => {
const rumour = createRumour(text, getPublicKey(senderSK), receiverPK);
const sealed = sealRumourSK(rumour, senderSK, wrapPK || receiverPK);
- return giftWrap(sealed, wrapPK || receiverPK);
+ return giftWrap(sealed, wrapPK || receiverPK, expiryDays);
}
// Create a NIP-17 message using NIP-07, requiring no sender secret key.
// If wrapPK is specified and different than receiverPK,
// the message will be gift-wrapped to wrapPK
// but the rumour will be sent to receiverPK.
-export const createNIP17MessageNIP07 = async (text: string, senderPK: string, receiverPK: string, wrapPK: string = null) => {
+// If expiryDays is specified, the message will expire in that many days,
+// plus a random amount from 0 to 2 for security.
+// (expiration time = random creation time + expiryDays + 2 days)
+export const createNIP17MessageNIP07 = async (text: string, senderPK: string, receiverPK: string, wrapPK: string = null, expiryDays: number = null) => {
const rumour = createRumour(text, senderPK, receiverPK);
const sealed = await sealRumourNip07(rumour, wrapPK || receiverPK);
- return giftWrap(sealed, wrapPK || receiverPK);
+ return giftWrap(sealed, wrapPK || receiverPK, expiryDays);
}
const createRumour = (text: string, senderPK: string, receiverPK: string) => {
@@ -68,16 +71,22 @@ const sealRumourNip07 = async (rumour: object, receiverPK: string) => {
return window.nostr.signEvent(sealEvent);
}
-const giftWrap = (event: object, receiverPK: string) => {
+const giftWrap = (event: object, receiverPK: string, expiryDays: number = null) => {
const secretKey = generateSecretKey();
const SKString = bytesToHex(secretKey);
const conversationKey = u.getConversationKey(secretKey, receiverPK);
const encryptedEvent = encrypt(JSON.stringify(event), conversationKey);
+ const created = randomTimestamp();
+ const tags = [["p", receiverPK]];
+ if (expiryDays != null) {
+ const expiration = created + (expiryDays + 2) * 86400;
+ tags.push(["expiration", expiration.toString()]);
+ }
const giftWrapEvent = {
kind: 1059, // gift wrap
- created_at: randomTimestamp(),
- tags: [["p", receiverPK]],
+ created_at: created,
+ tags: tags,
pubkey: getPublicKey(secretKey),
content: encryptedEvent
}
diff --git a/src/routes/(app)/dm/[recipient]/+page.svelte b/src/routes/(app)/dm/[recipient]/+page.svelte
index 9bebae92..e0702bc3 100644
--- a/src/routes/(app)/dm/[recipient]/+page.svelte
+++ b/src/routes/(app)/dm/[recipient]/+page.svelte
@@ -61,6 +61,15 @@
return relays;
}
+ const expired = (event: object) => {
+ for (const tag of event.tags) {
+ if (tag[0] == "expiration") {
+ return (Date.now() / 1000) >= Number(tag[1]);
+ }
+ }
+ return false;
+ }
+
const loadEvents = async (relays: string[]) => {
const wrapped = await pool.querySync(
relays, { kinds: [1059], "#p": [user.pubkey], limit: DM_FETCH_LIMIT }
@@ -69,9 +78,11 @@
// intentionally decrypting sequentially to avoid having a bunch of popups
let decryptedRumours = new Map();
for (event of wrapped) {
- const rumour = await decryptMessage(event);
- const created = Math.floor(rumour.created_at / 86400);
- appendMultimap(decryptedRumours, created, rumour);
+ if (!expired(event)) {
+ const rumour = await decryptMessage(event);
+ const created = Math.floor(rumour.created_at / 86400);
+ appendMultimap(decryptedRumours, created, rumour);
+ } else console.log("relay contains expired event", event.id);
}
for (const [date, event] of decryptedRumours) {
event.sort((ev1, ev2) => ev1.created_at - ev2.created_at);
@@ -107,11 +118,15 @@
message, user.pubkey, recipient.pubkey, user.pubkey);
} else {
const sk = await getPrivateKey(user);
- event1 = libnip17.createNIP17MessageSK(message, sk, recipient.pubkey);
+ event1 = libnip17.createNIP17MessageSK(
+ message, sk, recipient.pubkey);
event2 = libnip17.createNIP17MessageSK(
message, sk, recipient.pubkey, user.pubkey);
}
+ console.log(Date.now() / 1000);
+ console.log(event2.tags);
+
const recipientRelays = await getPreferredRelays(recipient.pubkey);
const p1 = Promise.any(pool.publish(recipientRelays, event1));
const senderRelays = await getPreferredRelays(user.pubkey);