mirror of
https://github.com/BlowaterNostr/blowater.git
synced 2024-10-18 07:33:22 +00:00
Encrypted Custom App Data in IndexedDB (#66)
This commit is contained in:
parent
3b74649f3e
commit
e849cf98ef
38
UI/app.tsx
38
UI/app.tsx
@ -123,7 +123,6 @@ async function initProfileSyncer(
|
||||
|
||||
// Sync Custom App Data
|
||||
(async () => {
|
||||
const chan = new Channel<[NostrEvent, string]>();
|
||||
let subId = newSubID();
|
||||
let resp = await pool.newSub(
|
||||
subId,
|
||||
@ -135,31 +134,12 @@ async function initProfileSyncer(
|
||||
if (resp instanceof Error) {
|
||||
throw resp;
|
||||
}
|
||||
(async () => {
|
||||
for await (let { res: nostrMessage, url: relayUrl } of resp) {
|
||||
if (nostrMessage.type === "EVENT" && nostrMessage.event.content) {
|
||||
const event = nostrMessage.event;
|
||||
const decryptedEvent = await decryptNostrEvent(
|
||||
event,
|
||||
accountContext,
|
||||
accountContext.publicKey.hex,
|
||||
);
|
||||
if (decryptedEvent instanceof DecryptionFailure) {
|
||||
console.error(decryptedEvent);
|
||||
continue;
|
||||
}
|
||||
await chan.put([
|
||||
decryptedEvent,
|
||||
relayUrl,
|
||||
]);
|
||||
}
|
||||
for await (const { res, url } of resp) {
|
||||
if (res.type == "EVENT") {
|
||||
database.addEvent(res.event);
|
||||
}
|
||||
console.log("closed");
|
||||
})();
|
||||
return chan;
|
||||
})().then((customAppDataChan) => {
|
||||
database.syncEvents((e) => e.kind == NostrKind.CustomAppData, customAppDataChan);
|
||||
});
|
||||
}
|
||||
})();
|
||||
|
||||
///////////////////////////////////
|
||||
// Add relays to Connection Pool //
|
||||
@ -274,13 +254,13 @@ export function AppComponent(props: {
|
||||
let socialPostsPanel: VNode | undefined;
|
||||
if (model.navigationModel.activeNav == "Social") {
|
||||
const allUserInfo = getAllUsersInformation(app.database, myAccountCtx);
|
||||
console.log("AppComponent:getSocialPosts before", Date.now() - t);
|
||||
// console.log("AppComponent:getSocialPosts before", Date.now() - t);
|
||||
const socialPosts = getSocialPosts(app.database, allUserInfo);
|
||||
console.log("AppComponent:getSocialPosts after", Date.now() - t, Date.now());
|
||||
// console.log("AppComponent:getSocialPosts after", Date.now() - t, Date.now());
|
||||
let focusedContentGetter = () => {
|
||||
console.log("AppComponent:getFocusedContent before", Date.now() - t);
|
||||
// console.log("AppComponent:getFocusedContent before", Date.now() - t);
|
||||
let _ = getFocusedContent(model.social.focusedContent, allUserInfo, socialPosts);
|
||||
console.log("AppComponent:getFocusedContent", Date.now() - t);
|
||||
// console.log("AppComponent:getFocusedContent", Date.now() - t);
|
||||
if (_?.type === "MessageThread") {
|
||||
let editor = model.social.replyEditors.get(_.data.root.event.id);
|
||||
if (editor == undefined) {
|
||||
|
@ -99,7 +99,7 @@ function socialPostsStream(pubkeys: Iterable<string>, pool: ConnectionPool) {
|
||||
|
||||
export function getAllUsersInformation(
|
||||
database: Database_Contextual_View,
|
||||
myAccountContext: NostrAccountContext,
|
||||
ctx: NostrAccountContext,
|
||||
): Map<string, UserInfo> {
|
||||
const t = Date.now();
|
||||
const res = new Map<string, UserInfo>();
|
||||
@ -140,10 +140,10 @@ export function getAllUsersInformation(
|
||||
case NostrKind.DIRECT_MESSAGE:
|
||||
{
|
||||
let whoAm_I_TalkingTo = "";
|
||||
if (event.pubkey == myAccountContext.publicKey.hex) {
|
||||
if (event.pubkey == ctx.publicKey.hex) {
|
||||
// I am the sender
|
||||
whoAm_I_TalkingTo = getTags(event).p[0];
|
||||
} else if (getTags(event).p[0] == myAccountContext.publicKey.hex) {
|
||||
} else if (getTags(event).p[0] == ctx.publicKey.hex) {
|
||||
// I am the receiver
|
||||
whoAm_I_TalkingTo = event.pubkey;
|
||||
} else {
|
||||
@ -152,7 +152,7 @@ export function getAllUsersInformation(
|
||||
}
|
||||
const userInfo = res.get(whoAm_I_TalkingTo);
|
||||
if (userInfo) {
|
||||
if (whoAm_I_TalkingTo == myAccountContext.publicKey.hex) {
|
||||
if (whoAm_I_TalkingTo == ctx.publicKey.hex) {
|
||||
// talking to myself
|
||||
if (userInfo.newestEventSendByMe) {
|
||||
if (event.created_at > userInfo.newestEventSendByMe?.created_at) {
|
||||
@ -164,7 +164,7 @@ export function getAllUsersInformation(
|
||||
userInfo.newestEventReceivedByMe = event;
|
||||
}
|
||||
} else {
|
||||
if (myAccountContext.publicKey.hex == event.pubkey) {
|
||||
if (ctx.publicKey.hex == event.pubkey) {
|
||||
// I am the sender
|
||||
if (userInfo.newestEventSendByMe) {
|
||||
if (event.created_at > userInfo.newestEventSendByMe.created_at) {
|
||||
@ -192,12 +192,12 @@ export function getAllUsersInformation(
|
||||
newestEventSendByMe: undefined,
|
||||
profile: undefined,
|
||||
};
|
||||
if (whoAm_I_TalkingTo == myAccountContext.publicKey.hex) {
|
||||
if (whoAm_I_TalkingTo == ctx.publicKey.hex) {
|
||||
// talking to myself
|
||||
newUserInfo.newestEventSendByMe = event;
|
||||
newUserInfo.newestEventReceivedByMe = event;
|
||||
} else {
|
||||
if (myAccountContext.publicKey.hex == event.pubkey) {
|
||||
if (ctx.publicKey.hex == event.pubkey) {
|
||||
// I am the sender
|
||||
newUserInfo.newestEventSendByMe = event;
|
||||
} else {
|
||||
@ -212,7 +212,10 @@ export function getAllUsersInformation(
|
||||
case NostrKind.DELETE:
|
||||
break;
|
||||
case NostrKind.CustomAppData: {
|
||||
const obj: CustomAppData = JSON.parse(event.content);
|
||||
if (event.kind == NostrKind.CustomAppData) {
|
||||
event;
|
||||
}
|
||||
const obj: CustomAppData = JSON.parse(event.decryptedContent);
|
||||
if (obj.type == "PinContact" || obj.type == "UnpinContact") {
|
||||
const userInfo = res.get(obj.pubkey);
|
||||
if (userInfo) {
|
||||
|
@ -1,5 +1,4 @@
|
||||
import * as dexie from "https://unpkg.com/dexie@3.2.3/dist/modern/dexie.mjs";
|
||||
import { Database_Contextual_View, Indices } from "../database.ts";
|
||||
import { NostrEvent } from "https://raw.githubusercontent.com/BlowaterNostr/nostr.ts/main/nostr.ts";
|
||||
|
||||
export class DexieDatabase extends dexie.Dexie {
|
||||
|
75
database.ts
75
database.ts
@ -10,7 +10,7 @@ import {
|
||||
NostrKind,
|
||||
RelayResponse_REQ_Message,
|
||||
} from "https://raw.githubusercontent.com/BlowaterNostr/nostr.ts/main/nostr.ts";
|
||||
import { getTags, Tag } from "./nostr.ts";
|
||||
import { Decrypted_Nostr_Event, getTags, PlainText_Nostr_Event, Tag } from "./nostr.ts";
|
||||
import * as csp from "https://raw.githubusercontent.com/BlowaterNostr/csp/master/csp.ts";
|
||||
import { DexieDatabase } from "./UI/dexie-db.ts";
|
||||
|
||||
@ -25,17 +25,30 @@ export interface Indices {
|
||||
}
|
||||
|
||||
export class Database_Contextual_View {
|
||||
private readonly sourceOfChange = csp.chan<NostrEvent>(buffer_size);
|
||||
private readonly caster = csp.multi<NostrEvent>(this.sourceOfChange);
|
||||
private readonly sourceOfChange = csp.chan<Decrypted_Nostr_Event | PlainText_Nostr_Event>(buffer_size);
|
||||
private readonly caster = csp.multi<Decrypted_Nostr_Event | PlainText_Nostr_Event>(this.sourceOfChange);
|
||||
|
||||
static async New(database: DexieDatabase, ctx: NostrAccountContext) {
|
||||
const cache: NostrEvent[] = await database.events.filter((_: any) => true).toArray();
|
||||
const events: NostrEvent[] = await database.events.filter((_: any) => true).toArray();
|
||||
const cache = new Array<PlainText_Nostr_Event | Decrypted_Nostr_Event>();
|
||||
for (const event of events) {
|
||||
const e = await transformEvent(event, ctx);
|
||||
if (e == undefined) {
|
||||
continue;
|
||||
}
|
||||
if (e instanceof Error) {
|
||||
console.log("Database:delete", event.id);
|
||||
database.events.delete(event.id);
|
||||
continue;
|
||||
}
|
||||
cache.push(e);
|
||||
}
|
||||
return new Database_Contextual_View(database, cache, ctx);
|
||||
}
|
||||
|
||||
constructor(
|
||||
private readonly database: DexieDatabase,
|
||||
private readonly cache: NostrEvent[],
|
||||
private readonly cache: (PlainText_Nostr_Event | Decrypted_Nostr_Event)[],
|
||||
private readonly ctx: NostrAccountContext,
|
||||
) {}
|
||||
|
||||
@ -47,19 +60,23 @@ export class Database_Contextual_View {
|
||||
return this.cache.filter(filter);
|
||||
};
|
||||
|
||||
private readonly addToIndexedDB = async (event: NostrEvent) => {
|
||||
await this.database.events.put(event);
|
||||
this.cache.push(event);
|
||||
};
|
||||
|
||||
async addEvent(event: NostrEvent) {
|
||||
const storedEvent = await this.getEvent({ id: event.id });
|
||||
if (storedEvent) { // event exist
|
||||
return;
|
||||
}
|
||||
console.log("Database.addEvent", event.id);
|
||||
await this.addToIndexedDB(event);
|
||||
await this.sourceOfChange.put(event);
|
||||
await this.database.events.put(event);
|
||||
const e = await transformEvent(event, this.ctx);
|
||||
if (e) {
|
||||
if (e instanceof Error) {
|
||||
console.log("Database:delete", event.id);
|
||||
this.database.events.delete(event.id);
|
||||
return;
|
||||
}
|
||||
this.cache.push(e);
|
||||
await this.sourceOfChange.put(e);
|
||||
}
|
||||
}
|
||||
|
||||
syncEvents(
|
||||
@ -219,3 +236,37 @@ export function whoIamTalkingTo(event: NostrEvent, myPublicKey: PublicKey) {
|
||||
// I am the receiver
|
||||
return whoIAmTalkingTo;
|
||||
}
|
||||
|
||||
async function transformEvent(event: NostrEvent, ctx: NostrAccountContext) {
|
||||
if (event.kind == NostrKind.CustomAppData) {
|
||||
if (event.pubkey == ctx.publicKey.hex) {
|
||||
// if I am the author
|
||||
const decrypted = await ctx.decrypt(ctx.publicKey.hex, event.content);
|
||||
if (decrypted instanceof Error) {
|
||||
return decrypted;
|
||||
}
|
||||
const e: Decrypted_Nostr_Event = {
|
||||
content: event.content,
|
||||
created_at: event.created_at,
|
||||
id: event.id,
|
||||
kind: event.kind,
|
||||
pubkey: event.pubkey,
|
||||
sig: event.sig,
|
||||
tags: event.tags,
|
||||
decryptedContent: decrypted,
|
||||
};
|
||||
return e;
|
||||
}
|
||||
} else {
|
||||
const e: PlainText_Nostr_Event = {
|
||||
content: event.content,
|
||||
created_at: event.created_at,
|
||||
id: event.id,
|
||||
kind: event.kind,
|
||||
pubkey: event.pubkey,
|
||||
sig: event.sig,
|
||||
tags: event.tags,
|
||||
};
|
||||
return e;
|
||||
}
|
||||
}
|
||||
|
33
nostr.ts
33
nostr.ts
@ -1,11 +1,7 @@
|
||||
/*
|
||||
Extension to common Nostr types
|
||||
*/
|
||||
import {
|
||||
PrivateKey,
|
||||
PublicKey,
|
||||
publicKeyHexFromNpub,
|
||||
} from "https://raw.githubusercontent.com/BlowaterNostr/nostr.ts/main/key.ts";
|
||||
import { PrivateKey, PublicKey } from "https://raw.githubusercontent.com/BlowaterNostr/nostr.ts/main/key.ts";
|
||||
import * as nostr from "https://raw.githubusercontent.com/BlowaterNostr/nostr.ts/main/nostr.ts";
|
||||
import {
|
||||
groupBy,
|
||||
@ -325,6 +321,33 @@ export interface Unsigned_CustomAppData_Typed_Event {
|
||||
readonly content: CustomAppData;
|
||||
}
|
||||
|
||||
export type Decrypted_Nostr_Event = {
|
||||
readonly id: nostr.EventID;
|
||||
readonly sig: string;
|
||||
readonly pubkey: string;
|
||||
readonly kind: nostr.NostrKind.CustomAppData;
|
||||
readonly created_at: number;
|
||||
readonly tags: Tag[];
|
||||
readonly content: string;
|
||||
readonly decryptedContent: string;
|
||||
};
|
||||
|
||||
export type PlainText_Nostr_Event = {
|
||||
readonly id: nostr.EventID;
|
||||
readonly sig: string;
|
||||
readonly pubkey: string;
|
||||
readonly kind:
|
||||
| NostrKind.DIRECT_MESSAGE
|
||||
| NostrKind.CONTACTS
|
||||
| NostrKind.DELETE
|
||||
| NostrKind.META_DATA
|
||||
| NostrKind.TEXT_NOTE
|
||||
| NostrKind.RECOMMED_SERVER;
|
||||
readonly created_at: number;
|
||||
readonly tags: Tag[];
|
||||
readonly content: string;
|
||||
};
|
||||
|
||||
export type CustomAppData = PinContact | UnpinContact | UserLogin;
|
||||
|
||||
export type PinContact = {
|
||||
|
Loading…
Reference in New Issue
Block a user