Merge pull request 'feat: gossip model' (#567) from gossip-model into main
Reviewed-on: #567
This commit is contained in:
commit
f1e089b0a1
3
.gitignore
vendored
3
.gitignore
vendored
@ -2,3 +2,6 @@ node_modules/
|
|||||||
.idea
|
.idea
|
||||||
.yarn
|
.yarn
|
||||||
yarn.lock
|
yarn.lock
|
||||||
|
dist/
|
||||||
|
*.tgz
|
||||||
|
*.log
|
@ -4,8 +4,9 @@
|
|||||||
"packages/*"
|
"packages/*"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "yarn workspace @snort/nostr build && yarn workspace @snort/app build",
|
"build": "yarn workspace @snort/system build && yarn workspace @snort/app build",
|
||||||
"start": "yarn workspace @snort/nostr build && yarn workspace @snort/app start"
|
"start": "yarn workspace @snort/system build && yarn workspace @snort/app start",
|
||||||
|
"test": "yarn workspace @snort/app test"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tauri-apps/cli": "^1.2.3",
|
"@tauri-apps/cli": "^1.2.3",
|
||||||
|
2
packages/app/.gitignore
vendored
2
packages/app/.gitignore
vendored
@ -23,3 +23,5 @@ yarn-debug.log*
|
|||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
|
|
||||||
.idea
|
.idea
|
||||||
|
|
||||||
|
dist/
|
||||||
|
9
packages/app/jest.config.js
Normal file
9
packages/app/jest.config.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
/** @type {import('ts-jest').JestConfigWithTsJest} */
|
||||||
|
module.exports = {
|
||||||
|
bail: true,
|
||||||
|
preset: "ts-jest",
|
||||||
|
testEnvironment: "jsdom",
|
||||||
|
roots: ["src"],
|
||||||
|
moduleDirectories: ["src", "node_modules"],
|
||||||
|
setupFiles: ["./src/setupTests.ts"],
|
||||||
|
};
|
@ -12,11 +12,11 @@
|
|||||||
"@reduxjs/toolkit": "^1.9.1",
|
"@reduxjs/toolkit": "^1.9.1",
|
||||||
"@scure/bip32": "^1.3.0",
|
"@scure/bip32": "^1.3.0",
|
||||||
"@scure/bip39": "^1.1.1",
|
"@scure/bip39": "^1.1.1",
|
||||||
"@snort/nostr": "^1.0.0",
|
|
||||||
"@szhsin/react-menu": "^3.3.1",
|
"@szhsin/react-menu": "^3.3.1",
|
||||||
"@void-cat/api": "^1.0.4",
|
"@void-cat/api": "^1.0.4",
|
||||||
"base32-decode": "^1.0.0",
|
"base32-decode": "^1.0.0",
|
||||||
"bech32": "^2.0.0",
|
"bech32": "^2.0.0",
|
||||||
|
"debug": "^4.3.4",
|
||||||
"dexie": "^3.2.2",
|
"dexie": "^3.2.2",
|
||||||
"dexie-react-hooks": "^1.1.1",
|
"dexie-react-hooks": "^1.1.1",
|
||||||
"dns-over-http-resolver": "^2.1.1",
|
"dns-over-http-resolver": "^2.1.1",
|
||||||
@ -48,7 +48,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "webpack serve",
|
"start": "webpack serve",
|
||||||
"build": "webpack --node-env=production",
|
"build": "webpack --node-env=production",
|
||||||
"test": "",
|
"test": "jest --runInBand",
|
||||||
"intl-extract": "formatjs extract 'src/**/*.ts*' --ignore='**/*.d.ts' --out-file src/lang.json --flatten true",
|
"intl-extract": "formatjs extract 'src/**/*.ts*' --ignore='**/*.d.ts' --out-file src/lang.json --flatten true",
|
||||||
"intl-compile": "formatjs compile src/lang.json --out-file src/translations/en.json",
|
"intl-compile": "formatjs compile src/lang.json --out-file src/translations/en.json",
|
||||||
"format": "prettier --write .",
|
"format": "prettier --write .",
|
||||||
@ -80,7 +80,8 @@
|
|||||||
"@babel/preset-react": "^7.18.6",
|
"@babel/preset-react": "^7.18.6",
|
||||||
"@formatjs/cli": "^6.0.1",
|
"@formatjs/cli": "^6.0.1",
|
||||||
"@formatjs/ts-transformer": "^3.13.1",
|
"@formatjs/ts-transformer": "^3.13.1",
|
||||||
"@types/jest": "^29.2.5",
|
"@types/debug": "^4.1.8",
|
||||||
|
"@types/jest": "^29.5.1",
|
||||||
"@types/node": "^18.11.18",
|
"@types/node": "^18.11.18",
|
||||||
"@types/react": "^18.0.26",
|
"@types/react": "^18.0.26",
|
||||||
"@types/react-dom": "^18.0.10",
|
"@types/react-dom": "^18.0.10",
|
||||||
@ -99,9 +100,12 @@
|
|||||||
"eslint-webpack-plugin": "^4.0.1",
|
"eslint-webpack-plugin": "^4.0.1",
|
||||||
"html-webpack-plugin": "^5.5.1",
|
"html-webpack-plugin": "^5.5.1",
|
||||||
"husky": ">=6",
|
"husky": ">=6",
|
||||||
|
"jest": "^29.5.0",
|
||||||
|
"jest-environment-jsdom": "^29.5.0",
|
||||||
"lint-staged": ">=10",
|
"lint-staged": ">=10",
|
||||||
"mini-css-extract-plugin": "^2.7.5",
|
"mini-css-extract-plugin": "^2.7.5",
|
||||||
"prettier": "2.8.3",
|
"prettier": "2.8.3",
|
||||||
|
"ts-jest": "^29.1.0",
|
||||||
"ts-loader": "^9.4.2",
|
"ts-loader": "^9.4.2",
|
||||||
"typescript": "^5.0.4",
|
"typescript": "^5.0.4",
|
||||||
"webpack": "^5.82.1",
|
"webpack": "^5.82.1",
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import { RawEvent } from "@snort/nostr";
|
import { NostrEvent } from "@snort/system";
|
||||||
import { db } from "Db";
|
import { db } from "Db";
|
||||||
import FeedCache from "./FeedCache";
|
import FeedCache from "./FeedCache";
|
||||||
|
|
||||||
class DMCache extends FeedCache<RawEvent> {
|
class DMCache extends FeedCache<NostrEvent> {
|
||||||
constructor() {
|
constructor() {
|
||||||
super("DMCache", db.dms);
|
super("DMCache", db.dms);
|
||||||
}
|
}
|
||||||
|
|
||||||
key(of: RawEvent): string {
|
key(of: NostrEvent): string {
|
||||||
return of.id;
|
return of.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -23,11 +23,11 @@ class DMCache extends FeedCache<RawEvent> {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
allDms(): Array<RawEvent> {
|
allDms(): Array<NostrEvent> {
|
||||||
return [...this.cache.values()];
|
return [...this.cache.values()];
|
||||||
}
|
}
|
||||||
|
|
||||||
takeSnapshot(): Array<RawEvent> {
|
takeSnapshot(): Array<NostrEvent> {
|
||||||
return this.allDms();
|
return this.allDms();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { db, EventInteraction } from "Db";
|
import { db, EventInteraction } from "Db";
|
||||||
import { LoginStore } from "Login";
|
import { LoginStore } from "Login";
|
||||||
import { sha256 } from "Util";
|
import { sha256 } from "SnortUtils";
|
||||||
import FeedCache from "./FeedCache";
|
import FeedCache from "./FeedCache";
|
||||||
|
|
||||||
class EventInteractionCache extends FeedCache<EventInteraction> {
|
class EventInteractionCache extends FeedCache<EventInteraction> {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { db } from "Db";
|
import { db } from "Db";
|
||||||
|
import debug from "debug";
|
||||||
import { Table } from "dexie";
|
import { Table } from "dexie";
|
||||||
import { unixNowMs, unwrap } from "Util";
|
import { unixNowMs, unwrap } from "SnortUtils";
|
||||||
|
|
||||||
type HookFn = () => void;
|
type HookFn = () => void;
|
||||||
|
|
||||||
@ -11,31 +12,32 @@ interface HookFilter {
|
|||||||
|
|
||||||
export default abstract class FeedCache<TCached> {
|
export default abstract class FeedCache<TCached> {
|
||||||
#name: string;
|
#name: string;
|
||||||
#table: Table<TCached>;
|
|
||||||
#hooks: Array<HookFilter> = [];
|
#hooks: Array<HookFilter> = [];
|
||||||
#snapshot: Readonly<Array<TCached>> = [];
|
#snapshot: Readonly<Array<TCached>> = [];
|
||||||
#changed = true;
|
#changed = true;
|
||||||
#hits = 0;
|
#hits = 0;
|
||||||
#miss = 0;
|
#miss = 0;
|
||||||
|
protected table: Table<TCached>;
|
||||||
protected onTable: Set<string> = new Set();
|
protected onTable: Set<string> = new Set();
|
||||||
protected cache: Map<string, TCached> = new Map();
|
protected cache: Map<string, TCached> = new Map();
|
||||||
|
|
||||||
constructor(name: string, table: Table<TCached>) {
|
constructor(name: string, table: Table<TCached>) {
|
||||||
this.#name = name;
|
this.#name = name;
|
||||||
this.#table = table;
|
this.table = table;
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
console.debug(
|
debug(this.#name)(
|
||||||
`[${this.#name}] ${this.cache.size} loaded, ${this.onTable.size} on-disk, ${this.#hooks.length} hooks, ${(
|
"%d loaded, %d on-disk, %d hooks, %d% hit",
|
||||||
(this.#hits / (this.#hits + this.#miss)) *
|
this.cache.size,
|
||||||
100
|
this.onTable.size,
|
||||||
).toFixed(1)} % hit`
|
this.#hooks.length,
|
||||||
|
((this.#hits / (this.#hits + this.#miss)) * 100).toFixed(1)
|
||||||
);
|
);
|
||||||
}, 5_000);
|
}, 30_000);
|
||||||
}
|
}
|
||||||
|
|
||||||
async preload() {
|
async preload() {
|
||||||
if (db.ready) {
|
if (db.ready) {
|
||||||
const keys = await this.#table.toCollection().primaryKeys();
|
const keys = await this.table.toCollection().primaryKeys();
|
||||||
this.onTable = new Set<string>(keys.map(a => a as string));
|
this.onTable = new Set<string>(keys.map(a => a as string));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -73,7 +75,7 @@ export default abstract class FeedCache<TCached> {
|
|||||||
|
|
||||||
async get(key?: string) {
|
async get(key?: string) {
|
||||||
if (key && !this.cache.has(key) && db.ready) {
|
if (key && !this.cache.has(key) && db.ready) {
|
||||||
const cached = await this.#table.get(key);
|
const cached = await this.table.get(key);
|
||||||
if (cached) {
|
if (cached) {
|
||||||
this.cache.set(this.key(cached), cached);
|
this.cache.set(this.key(cached), cached);
|
||||||
this.notifyChange([key]);
|
this.notifyChange([key]);
|
||||||
@ -86,7 +88,7 @@ export default abstract class FeedCache<TCached> {
|
|||||||
async bulkGet(keys: Array<string>) {
|
async bulkGet(keys: Array<string>) {
|
||||||
const missing = keys.filter(a => !this.cache.has(a));
|
const missing = keys.filter(a => !this.cache.has(a));
|
||||||
if (missing.length > 0 && db.ready) {
|
if (missing.length > 0 && db.ready) {
|
||||||
const cached = await this.#table.bulkGet(missing);
|
const cached = await this.table.bulkGet(missing);
|
||||||
cached.forEach(a => {
|
cached.forEach(a => {
|
||||||
if (a) {
|
if (a) {
|
||||||
this.cache.set(this.key(a), a);
|
this.cache.set(this.key(a), a);
|
||||||
@ -103,7 +105,7 @@ export default abstract class FeedCache<TCached> {
|
|||||||
const k = this.key(obj);
|
const k = this.key(obj);
|
||||||
this.cache.set(k, obj);
|
this.cache.set(k, obj);
|
||||||
if (db.ready) {
|
if (db.ready) {
|
||||||
await this.#table.put(obj);
|
await this.table.put(obj);
|
||||||
this.onTable.add(k);
|
this.onTable.add(k);
|
||||||
}
|
}
|
||||||
this.notifyChange([k]);
|
this.notifyChange([k]);
|
||||||
@ -111,13 +113,44 @@ export default abstract class FeedCache<TCached> {
|
|||||||
|
|
||||||
async bulkSet(obj: Array<TCached>) {
|
async bulkSet(obj: Array<TCached>) {
|
||||||
if (db.ready) {
|
if (db.ready) {
|
||||||
await this.#table.bulkPut(obj);
|
await this.table.bulkPut(obj);
|
||||||
obj.forEach(a => this.onTable.add(this.key(a)));
|
obj.forEach(a => this.onTable.add(this.key(a)));
|
||||||
}
|
}
|
||||||
obj.forEach(v => this.cache.set(this.key(v), v));
|
obj.forEach(v => this.cache.set(this.key(v), v));
|
||||||
this.notifyChange(obj.map(a => this.key(a)));
|
this.notifyChange(obj.map(a => this.key(a)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to update an entry where created values exists
|
||||||
|
* @param m Profile metadata
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
async update<TCachedWithCreated extends TCached & { created: number; loaded: number }>(m: TCachedWithCreated) {
|
||||||
|
const k = this.key(m);
|
||||||
|
const existing = this.getFromCache(k) as TCachedWithCreated;
|
||||||
|
const updateType = (() => {
|
||||||
|
if (!existing) {
|
||||||
|
return "new";
|
||||||
|
}
|
||||||
|
if (existing.created < m.created) {
|
||||||
|
return "updated";
|
||||||
|
}
|
||||||
|
if (existing && existing.loaded < m.loaded) {
|
||||||
|
return "refresh";
|
||||||
|
}
|
||||||
|
return "no_change";
|
||||||
|
})();
|
||||||
|
debug(this.#name)("Updating %s %s %o", k, updateType, m);
|
||||||
|
if (updateType !== "no_change") {
|
||||||
|
const updated = {
|
||||||
|
...existing,
|
||||||
|
...m,
|
||||||
|
};
|
||||||
|
await this.set(updated);
|
||||||
|
}
|
||||||
|
return updateType;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads a list of rows from disk cache
|
* Loads a list of rows from disk cache
|
||||||
* @param keys List of ids to load
|
* @param keys List of ids to load
|
||||||
@ -131,16 +164,17 @@ export default abstract class FeedCache<TCached> {
|
|||||||
key: a,
|
key: a,
|
||||||
}));
|
}));
|
||||||
const start = unixNowMs();
|
const start = unixNowMs();
|
||||||
const fromCache = await this.#table.bulkGet(mapped.filter(a => a.has).map(a => a.key));
|
const fromCache = await this.table.bulkGet(mapped.filter(a => a.has).map(a => a.key));
|
||||||
const fromCacheFiltered = fromCache.filter(a => a !== undefined).map(a => unwrap(a));
|
const fromCacheFiltered = fromCache.filter(a => a !== undefined).map(a => unwrap(a));
|
||||||
fromCacheFiltered.forEach(a => {
|
fromCacheFiltered.forEach(a => {
|
||||||
this.cache.set(this.key(a), a);
|
this.cache.set(this.key(a), a);
|
||||||
});
|
});
|
||||||
this.notifyChange(fromCacheFiltered.map(a => this.key(a)));
|
this.notifyChange(fromCacheFiltered.map(a => this.key(a)));
|
||||||
console.debug(
|
debug(this.#name)(
|
||||||
`[${this.#name}] Loaded ${fromCacheFiltered.length}/${keys.length} in ${(
|
`Loaded %d/%d in %d ms`,
|
||||||
unixNowMs() - start
|
fromCacheFiltered.length,
|
||||||
).toLocaleString()} ms`
|
keys.length,
|
||||||
|
(unixNowMs() - start).toLocaleString()
|
||||||
);
|
);
|
||||||
return mapped.filter(a => !a.has).map(a => a.key);
|
return mapped.filter(a => !a.has).map(a => a.key);
|
||||||
}
|
}
|
||||||
@ -150,7 +184,7 @@ export default abstract class FeedCache<TCached> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async clear() {
|
async clear() {
|
||||||
await this.#table.clear();
|
await this.table.clear();
|
||||||
this.cache.clear();
|
this.cache.clear();
|
||||||
this.onTable.clear();
|
this.onTable.clear();
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import FeedCache from "Cache/FeedCache";
|
import FeedCache from "Cache/FeedCache";
|
||||||
import { db } from "Db";
|
import { db } from "Db";
|
||||||
import { MetadataCache } from "Cache";
|
import { MetadataCache } from "@snort/system";
|
||||||
import { LNURL } from "LNURL";
|
import { LNURL } from "LNURL";
|
||||||
import { fetchNip05Pubkey } from "Nip05/Verifier";
|
import { fetchNip05Pubkey } from "Nip05/Verifier";
|
||||||
|
|
||||||
@ -18,6 +18,14 @@ class UserProfileCache extends FeedCache<MetadataCache> {
|
|||||||
return of.pubkey;
|
return of.pubkey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override async preload(follows?: Array<string>): Promise<void> {
|
||||||
|
await super.preload();
|
||||||
|
// load follows profiles
|
||||||
|
if (follows) {
|
||||||
|
await this.buffer(follows);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async search(q: string): Promise<Array<MetadataCache>> {
|
async search(q: string): Promise<Array<MetadataCache>> {
|
||||||
if (db.ready) {
|
if (db.ready) {
|
||||||
// on-disk cache will always have more data
|
// on-disk cache will always have more data
|
||||||
@ -53,35 +61,15 @@ class UserProfileCache extends FeedCache<MetadataCache> {
|
|||||||
* @param m Profile metadata
|
* @param m Profile metadata
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
async update(m: MetadataCache) {
|
override async update(m: MetadataCache) {
|
||||||
const existing = this.getFromCache(m.pubkey);
|
const updateType = await super.update(m);
|
||||||
const updateType = (() => {
|
if (updateType !== "refresh") {
|
||||||
if (!existing) {
|
const lnurl = m.lud16 ?? m.lud06;
|
||||||
return "new_profile";
|
if (lnurl) {
|
||||||
}
|
this.#zapperQueue.push({
|
||||||
if (existing.created < m.created) {
|
pubkey: m.pubkey,
|
||||||
return "updated_profile";
|
lnurl,
|
||||||
}
|
});
|
||||||
if (existing && existing.loaded < m.loaded) {
|
|
||||||
return "refresh_profile";
|
|
||||||
}
|
|
||||||
return "no_change";
|
|
||||||
})();
|
|
||||||
console.debug(`Updating ${m.pubkey} ${updateType}`, m);
|
|
||||||
if (updateType !== "no_change") {
|
|
||||||
const writeProfile = {
|
|
||||||
...existing,
|
|
||||||
...m,
|
|
||||||
};
|
|
||||||
await this.#setItem(writeProfile);
|
|
||||||
if (updateType !== "refresh_profile") {
|
|
||||||
const lnurl = m.lud16 ?? m.lud06;
|
|
||||||
if (lnurl) {
|
|
||||||
this.#zapperQueue.push({
|
|
||||||
pubkey: m.pubkey,
|
|
||||||
lnurl,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (m.nip05) {
|
if (m.nip05) {
|
||||||
this.#nip5Queue.push({
|
this.#nip5Queue.push({
|
||||||
@ -97,15 +85,6 @@ class UserProfileCache extends FeedCache<MetadataCache> {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
async #setItem(m: MetadataCache) {
|
|
||||||
this.cache.set(m.pubkey, m);
|
|
||||||
if (db.ready) {
|
|
||||||
await db.users.put(m);
|
|
||||||
this.onTable.add(m.pubkey);
|
|
||||||
}
|
|
||||||
this.notifyChange([m.pubkey]);
|
|
||||||
}
|
|
||||||
|
|
||||||
async #processZapperQueue() {
|
async #processZapperQueue() {
|
||||||
await this.#batchQueue(
|
await this.#batchQueue(
|
||||||
this.#zapperQueue,
|
this.#zapperQueue,
|
||||||
@ -114,7 +93,7 @@ class UserProfileCache extends FeedCache<MetadataCache> {
|
|||||||
await svc.load();
|
await svc.load();
|
||||||
const p = this.getFromCache(i.pubkey);
|
const p = this.getFromCache(i.pubkey);
|
||||||
if (p) {
|
if (p) {
|
||||||
this.#setItem({
|
await this.set({
|
||||||
...p,
|
...p,
|
||||||
zapService: svc.zapperPubkey,
|
zapService: svc.zapperPubkey,
|
||||||
});
|
});
|
||||||
@ -134,7 +113,7 @@ class UserProfileCache extends FeedCache<MetadataCache> {
|
|||||||
const nip5pk = await fetchNip05Pubkey(name, domain);
|
const nip5pk = await fetchNip05Pubkey(name, domain);
|
||||||
const p = this.getFromCache(i.pubkey);
|
const p = this.getFromCache(i.pubkey);
|
||||||
if (p) {
|
if (p) {
|
||||||
this.#setItem({
|
await this.set({
|
||||||
...p,
|
...p,
|
||||||
isNostrAddressValid: i.pubkey === nip5pk,
|
isNostrAddressValid: i.pubkey === nip5pk,
|
||||||
});
|
});
|
||||||
|
31
packages/app/src/Cache/UserRelayCache.ts
Normal file
31
packages/app/src/Cache/UserRelayCache.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { db, UsersRelays } from "Db";
|
||||||
|
import FeedCache from "./FeedCache";
|
||||||
|
|
||||||
|
export class UsersRelaysCache extends FeedCache<UsersRelays> {
|
||||||
|
constructor() {
|
||||||
|
super("UserRelays", db.userRelays);
|
||||||
|
}
|
||||||
|
|
||||||
|
key(of: UsersRelays): string {
|
||||||
|
return of.pubkey;
|
||||||
|
}
|
||||||
|
|
||||||
|
override async preload(follows?: Array<string>): Promise<void> {
|
||||||
|
await super.preload();
|
||||||
|
if (follows) {
|
||||||
|
await this.buffer(follows);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
newest(): number {
|
||||||
|
let ret = 0;
|
||||||
|
this.cache.forEach(v => (ret = v.created_at > ret ? v.created_at : ret));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
takeSnapshot(): Array<UsersRelays> {
|
||||||
|
return [...this.cache.values()];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const UserRelays = new UsersRelaysCache();
|
@ -1,60 +1,16 @@
|
|||||||
import { HexKey, RawEvent, UserMetadata } from "@snort/nostr";
|
|
||||||
import { hexToBech32, unixNowMs } from "Util";
|
|
||||||
import { DmCache } from "./DMCache";
|
import { DmCache } from "./DMCache";
|
||||||
import { InteractionCache } from "./EventInteractionCache";
|
import { InteractionCache } from "./EventInteractionCache";
|
||||||
import { UserCache } from "./UserCache";
|
import { UserCache } from "./UserCache";
|
||||||
|
import { UserRelays } from "./UserRelayCache";
|
||||||
|
|
||||||
export interface MetadataCache extends UserMetadata {
|
export async function preload(follows?: Array<string>) {
|
||||||
/**
|
const preloads = [
|
||||||
* When the object was saved in cache
|
UserCache.preload(follows),
|
||||||
*/
|
DmCache.preload(),
|
||||||
loaded: number;
|
InteractionCache.preload(),
|
||||||
|
UserRelays.preload(follows),
|
||||||
/**
|
];
|
||||||
* When the source metadata event was created
|
await Promise.all(preloads);
|
||||||
*/
|
|
||||||
created: number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The pubkey of the owner of this metadata
|
|
||||||
*/
|
|
||||||
pubkey: HexKey;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The bech32 encoded pubkey
|
|
||||||
*/
|
|
||||||
npub: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pubkey of zapper service
|
|
||||||
*/
|
|
||||||
zapService?: HexKey;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If the nip05 is valid for this user
|
|
||||||
*/
|
|
||||||
isNostrAddressValid: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function mapEventToProfile(ev: RawEvent) {
|
|
||||||
try {
|
|
||||||
const data: UserMetadata = JSON.parse(ev.content);
|
|
||||||
return {
|
|
||||||
pubkey: ev.pubkey,
|
|
||||||
npub: hexToBech32("npub", ev.pubkey),
|
|
||||||
created: ev.created_at,
|
|
||||||
...data,
|
|
||||||
loaded: unixNowMs(),
|
|
||||||
} as MetadataCache;
|
|
||||||
} catch (e) {
|
|
||||||
console.error("Failed to parse JSON", ev, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function preload() {
|
|
||||||
await UserCache.preload();
|
|
||||||
await DmCache.preload();
|
|
||||||
await InteractionCache.preload();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export { UserCache, DmCache };
|
export { UserCache, DmCache };
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { RelaySettings } from "@snort/nostr";
|
import { RelaySettings } from "@snort/system";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add-on api for snort features
|
* Add-on api for snort features
|
||||||
@ -33,7 +33,7 @@ export const DefaultConnectTimeout = 2000;
|
|||||||
/**
|
/**
|
||||||
* How long profile cache should be considered valid for
|
* How long profile cache should be considered valid for
|
||||||
*/
|
*/
|
||||||
export const ProfileCacheExpire = 1_000 * 60 * 30;
|
export const ProfileCacheExpire = 1_000 * 60 * 60 * 6;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default bootstrap relays
|
* Default bootstrap relays
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import Dexie, { Table } from "dexie";
|
import Dexie, { Table } from "dexie";
|
||||||
import { FullRelaySettings, HexKey, RawEvent, u256 } from "@snort/nostr";
|
import { FullRelaySettings, HexKey, NostrEvent, u256, MetadataCache } from "@snort/system";
|
||||||
import { MetadataCache } from "Cache";
|
|
||||||
|
|
||||||
export const NAME = "snortDB";
|
export const NAME = "snortDB";
|
||||||
export const VERSION = 9;
|
export const VERSION = 9;
|
||||||
@ -21,6 +20,7 @@ export interface RelayMetrics {
|
|||||||
|
|
||||||
export interface UsersRelays {
|
export interface UsersRelays {
|
||||||
pubkey: HexKey;
|
pubkey: HexKey;
|
||||||
|
created_at: number;
|
||||||
relays: FullRelaySettings[];
|
relays: FullRelaySettings[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,8 +55,8 @@ export class SnortDB extends Dexie {
|
|||||||
users!: Table<MetadataCache>;
|
users!: Table<MetadataCache>;
|
||||||
relayMetrics!: Table<RelayMetrics>;
|
relayMetrics!: Table<RelayMetrics>;
|
||||||
userRelays!: Table<UsersRelays>;
|
userRelays!: Table<UsersRelays>;
|
||||||
events!: Table<RawEvent>;
|
events!: Table<NostrEvent>;
|
||||||
dms!: Table<RawEvent>;
|
dms!: Table<NostrEvent>;
|
||||||
eventInteraction!: Table<EventInteraction>;
|
eventInteraction!: Table<EventInteraction>;
|
||||||
payments!: Table<Payment>;
|
payments!: Table<Payment>;
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ import "./Avatar.css";
|
|||||||
import Nostrich from "nostrich.webp";
|
import Nostrich from "nostrich.webp";
|
||||||
|
|
||||||
import { CSSProperties, useEffect, useState } from "react";
|
import { CSSProperties, useEffect, useState } from "react";
|
||||||
import type { UserMetadata } from "@snort/nostr";
|
import type { UserMetadata } from "@snort/system";
|
||||||
|
|
||||||
import useImgProxy from "Hooks/useImgProxy";
|
import useImgProxy from "Hooks/useImgProxy";
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import Icon from "Icons/Icon";
|
import Icon from "Icons/Icon";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import useFileUpload from "Upload";
|
import useFileUpload from "Upload";
|
||||||
import { openFile, unwrap } from "Util";
|
import { openFile, unwrap } from "SnortUtils";
|
||||||
|
|
||||||
interface AvatarEditorProps {
|
interface AvatarEditorProps {
|
||||||
picture?: string;
|
picture?: string;
|
||||||
|
@ -3,13 +3,13 @@ import "./BadgeList.css";
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
|
|
||||||
import { TaggedRawEvent } from "@snort/nostr";
|
import { TaggedRawEvent } from "@snort/system";
|
||||||
|
|
||||||
import { ProxyImg } from "Element/ProxyImg";
|
import { ProxyImg } from "Element/ProxyImg";
|
||||||
import Icon from "Icons/Icon";
|
import Icon from "Icons/Icon";
|
||||||
import Modal from "Element/Modal";
|
import Modal from "Element/Modal";
|
||||||
import Username from "Element/Username";
|
import Username from "Element/Username";
|
||||||
import { findTag } from "Util";
|
import { findTag } from "SnortUtils";
|
||||||
|
|
||||||
export default function BadgeList({ badges }: { badges: TaggedRawEvent[] }) {
|
export default function BadgeList({ badges }: { badges: TaggedRawEvent[] }) {
|
||||||
const [showModal, setShowModal] = useState(false);
|
const [showModal, setShowModal] = useState(false);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
import { HexKey } from "@snort/nostr";
|
import { HexKey } from "@snort/system";
|
||||||
import useModeration from "Hooks/useModeration";
|
import useModeration from "Hooks/useModeration";
|
||||||
|
|
||||||
import messages from "./messages";
|
import messages from "./messages";
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { useState, useMemo, ChangeEvent } from "react";
|
import { useState, useMemo, ChangeEvent } from "react";
|
||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
import { HexKey, TaggedRawEvent } from "@snort/nostr";
|
import { HexKey, TaggedRawEvent } from "@snort/system";
|
||||||
|
|
||||||
import Note from "Element/Note";
|
import Note from "Element/Note";
|
||||||
import useLogin from "Hooks/useLogin";
|
import useLogin from "Hooks/useLogin";
|
||||||
|
@ -2,13 +2,13 @@ import "./DM.css";
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
import { useInView } from "react-intersection-observer";
|
import { useInView } from "react-intersection-observer";
|
||||||
import { TaggedRawEvent } from "@snort/nostr";
|
import { TaggedRawEvent } from "@snort/system";
|
||||||
|
|
||||||
import useEventPublisher from "Feed/EventPublisher";
|
import useEventPublisher from "Feed/EventPublisher";
|
||||||
import NoteTime from "Element/NoteTime";
|
import NoteTime from "Element/NoteTime";
|
||||||
import Text from "Element/Text";
|
import Text from "Element/Text";
|
||||||
import { setLastReadDm } from "Pages/MessagesPage";
|
import { setLastReadDm } from "Pages/MessagesPage";
|
||||||
import { unwrap } from "Util";
|
import { unwrap } from "SnortUtils";
|
||||||
import useLogin from "Hooks/useLogin";
|
import useLogin from "Hooks/useLogin";
|
||||||
|
|
||||||
import messages from "./messages";
|
import messages from "./messages";
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import "./DmWindow.css";
|
import "./DmWindow.css";
|
||||||
import { useEffect, useMemo, useRef } from "react";
|
import { useEffect, useMemo, useRef } from "react";
|
||||||
import { TaggedRawEvent } from "@snort/nostr";
|
import { TaggedRawEvent } from "@snort/system";
|
||||||
|
|
||||||
import ProfileImage from "Element/ProfileImage";
|
import ProfileImage from "Element/ProfileImage";
|
||||||
import DM from "Element/DM";
|
import DM from "Element/DM";
|
||||||
@ -9,7 +9,7 @@ import NoteToSelf from "Element/NoteToSelf";
|
|||||||
import { useDmCache } from "Hooks/useDmsCache";
|
import { useDmCache } from "Hooks/useDmsCache";
|
||||||
import useLogin from "Hooks/useLogin";
|
import useLogin from "Hooks/useLogin";
|
||||||
import WriteDm from "Element/WriteDm";
|
import WriteDm from "Element/WriteDm";
|
||||||
import { unwrap } from "Util";
|
import { unwrap } from "SnortUtils";
|
||||||
|
|
||||||
export default function DmWindow({ id }: { id: string }) {
|
export default function DmWindow({ id }: { id: string }) {
|
||||||
const pubKey = useLogin().publicKey;
|
const pubKey = useLogin().publicKey;
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import "./FollowButton.css";
|
import "./FollowButton.css";
|
||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
import { HexKey } from "@snort/nostr";
|
import { HexKey } from "@snort/system";
|
||||||
|
|
||||||
import useEventPublisher from "Feed/EventPublisher";
|
import useEventPublisher from "Feed/EventPublisher";
|
||||||
import { parseId } from "Util";
|
import { parseId } from "SnortUtils";
|
||||||
import useLogin from "Hooks/useLogin";
|
import useLogin from "Hooks/useLogin";
|
||||||
import AsyncButton from "Element/AsyncButton";
|
import AsyncButton from "Element/AsyncButton";
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { ReactNode } from "react";
|
import { ReactNode } from "react";
|
||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
import { HexKey } from "@snort/nostr";
|
import { HexKey } from "@snort/system";
|
||||||
|
|
||||||
import useEventPublisher from "Feed/EventPublisher";
|
import useEventPublisher from "Feed/EventPublisher";
|
||||||
import ProfilePreview from "Element/ProfilePreview";
|
import ProfilePreview from "Element/ProfilePreview";
|
||||||
|
@ -13,7 +13,7 @@ import {
|
|||||||
NostrNestsRegex,
|
NostrNestsRegex,
|
||||||
WavlakeRegex,
|
WavlakeRegex,
|
||||||
} from "Const";
|
} from "Const";
|
||||||
import { magnetURIDecode } from "Util";
|
import { magnetURIDecode } from "SnortUtils";
|
||||||
import SoundCloudEmbed from "Element/SoundCloudEmded";
|
import SoundCloudEmbed from "Element/SoundCloudEmded";
|
||||||
import MixCloudEmbed from "Element/MixCloudEmbed";
|
import MixCloudEmbed from "Element/MixCloudEmbed";
|
||||||
import SpotifyEmbed from "Element/SpotifyEmbed";
|
import SpotifyEmbed from "Element/SpotifyEmbed";
|
||||||
|
@ -6,7 +6,7 @@ import { useMemo } from "react";
|
|||||||
import SendSats from "Element/SendSats";
|
import SendSats from "Element/SendSats";
|
||||||
import Icon from "Icons/Icon";
|
import Icon from "Icons/Icon";
|
||||||
import { useWallet } from "Wallet";
|
import { useWallet } from "Wallet";
|
||||||
import { decodeInvoice } from "Util";
|
import { decodeInvoice } from "SnortUtils";
|
||||||
|
|
||||||
import messages from "./messages";
|
import messages from "./messages";
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
|
|
||||||
import { Magnet } from "Util";
|
import { Magnet } from "SnortUtils";
|
||||||
|
|
||||||
interface MagnetLinkProps {
|
interface MagnetLinkProps {
|
||||||
magnet: Magnet;
|
magnet: Magnet;
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { HexKey } from "@snort/nostr";
|
import { HexKey } from "@snort/system";
|
||||||
|
|
||||||
import { useUserProfile } from "Hooks/useUserProfile";
|
import { useUserProfile } from "Hooks/useUserProfile";
|
||||||
import { profileLink } from "Util";
|
import { profileLink } from "SnortUtils";
|
||||||
import { getDisplayName } from "Element/ProfileImage";
|
import { getDisplayName } from "Element/ProfileImage";
|
||||||
|
|
||||||
export default function Mention({ pubkey, relays }: { pubkey: HexKey; relays?: Array<string> | string }) {
|
export default function Mention({ pubkey, relays }: { pubkey: HexKey; relays?: Array<string> | string }) {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
import { HexKey } from "@snort/nostr";
|
import { HexKey } from "@snort/system";
|
||||||
import useModeration from "Hooks/useModeration";
|
import useModeration from "Hooks/useModeration";
|
||||||
|
|
||||||
import messages from "./messages";
|
import messages from "./messages";
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
import { HexKey } from "@snort/nostr";
|
import { HexKey } from "@snort/system";
|
||||||
import MuteButton from "Element/MuteButton";
|
import MuteButton from "Element/MuteButton";
|
||||||
import ProfilePreview from "Element/ProfilePreview";
|
import ProfilePreview from "Element/ProfilePreview";
|
||||||
import useModeration from "Hooks/useModeration";
|
import useModeration from "Hooks/useModeration";
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import "./Nip05.css";
|
import "./Nip05.css";
|
||||||
import { HexKey } from "@snort/nostr";
|
import { HexKey } from "@snort/system";
|
||||||
|
|
||||||
import Icon from "Icons/Icon";
|
import Icon from "Icons/Icon";
|
||||||
import { useUserProfile } from "Hooks/useUserProfile";
|
import { useUserProfile } from "Hooks/useUserProfile";
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { useEffect, useMemo, useState, ChangeEvent } from "react";
|
import { useEffect, useMemo, useState, ChangeEvent } from "react";
|
||||||
import { useIntl, FormattedMessage } from "react-intl";
|
import { useIntl, FormattedMessage } from "react-intl";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { UserMetadata } from "@snort/nostr";
|
import { UserMetadata, mapEventToProfile } from "@snort/system";
|
||||||
|
|
||||||
import { unwrap } from "Util";
|
import { unwrap } from "SnortUtils";
|
||||||
import { formatShort } from "Number";
|
import { formatShort } from "Number";
|
||||||
import {
|
import {
|
||||||
ServiceProvider,
|
ServiceProvider,
|
||||||
@ -19,10 +19,10 @@ import SendSats from "Element/SendSats";
|
|||||||
import Copy from "Element/Copy";
|
import Copy from "Element/Copy";
|
||||||
import { useUserProfile } from "Hooks/useUserProfile";
|
import { useUserProfile } from "Hooks/useUserProfile";
|
||||||
import useEventPublisher from "Feed/EventPublisher";
|
import useEventPublisher from "Feed/EventPublisher";
|
||||||
import { debounce } from "Util";
|
import { debounce } from "SnortUtils";
|
||||||
import useLogin from "Hooks/useLogin";
|
import useLogin from "Hooks/useLogin";
|
||||||
import SnortServiceProvider from "Nip05/SnortServiceProvider";
|
import SnortServiceProvider from "Nip05/SnortServiceProvider";
|
||||||
import { mapEventToProfile, UserCache } from "Cache";
|
import { UserCache } from "Cache";
|
||||||
|
|
||||||
import messages from "./messages";
|
import messages from "./messages";
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
import { RawEvent } from "@snort/nostr";
|
import { NostrEvent, NostrLink } from "@snort/system";
|
||||||
|
|
||||||
import { findTag, NostrLink } from "Util";
|
import { findTag } from "SnortUtils";
|
||||||
import useEventFeed from "Feed/EventFeed";
|
import useEventFeed from "Feed/EventFeed";
|
||||||
import PageSpinner from "Element/PageSpinner";
|
import PageSpinner from "Element/PageSpinner";
|
||||||
import Reveal from "Element/Reveal";
|
import Reveal from "Element/Reveal";
|
||||||
@ -14,7 +14,7 @@ export default function NostrFileHeader({ link }: { link: NostrLink }) {
|
|||||||
return <NostrFileElement ev={ev.data} />;
|
return <NostrFileElement ev={ev.data} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function NostrFileElement({ ev }: { ev: RawEvent }) {
|
export function NostrFileElement({ ev }: { ev: NostrEvent }) {
|
||||||
// assume image or embed which can be rendered by the hypertext kind
|
// assume image or embed which can be rendered by the hypertext kind
|
||||||
// todo: make use of hash
|
// todo: make use of hash
|
||||||
// todo: use magnet or other links if present
|
// todo: use magnet or other links if present
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { NostrPrefix } from "@snort/nostr";
|
import { NostrPrefix, parseNostrLink } from "@snort/system";
|
||||||
|
|
||||||
import Mention from "Element/Mention";
|
import Mention from "Element/Mention";
|
||||||
import { parseNostrLink } from "Util";
|
|
||||||
import NoteQuote from "Element/NoteQuote";
|
import NoteQuote from "Element/NoteQuote";
|
||||||
|
|
||||||
export default function NostrLink({ link, depth }: { link: string; depth?: number }) {
|
export default function NostrLink({ link, depth }: { link: string; depth?: number }) {
|
||||||
|
@ -3,7 +3,7 @@ import React, { useMemo, useState, useLayoutEffect, ReactNode } from "react";
|
|||||||
import { useNavigate, Link } from "react-router-dom";
|
import { useNavigate, Link } from "react-router-dom";
|
||||||
import { useInView } from "react-intersection-observer";
|
import { useInView } from "react-intersection-observer";
|
||||||
import { useIntl, FormattedMessage } from "react-intl";
|
import { useIntl, FormattedMessage } from "react-intl";
|
||||||
import { TaggedRawEvent, HexKey, EventKind, NostrPrefix, Lists } from "@snort/nostr";
|
import { TaggedRawEvent, HexKey, EventKind, NostrPrefix, Lists, EventExt } from "@snort/system";
|
||||||
|
|
||||||
import useEventPublisher from "Feed/EventPublisher";
|
import useEventPublisher from "Feed/EventPublisher";
|
||||||
import Icon from "Icons/Icon";
|
import Icon from "Icons/Icon";
|
||||||
@ -19,14 +19,13 @@ import {
|
|||||||
normalizeReaction,
|
normalizeReaction,
|
||||||
Reaction,
|
Reaction,
|
||||||
profileLink,
|
profileLink,
|
||||||
} from "Util";
|
} from "SnortUtils";
|
||||||
import NoteFooter, { Translation } from "Element/NoteFooter";
|
import NoteFooter, { Translation } from "Element/NoteFooter";
|
||||||
import NoteTime from "Element/NoteTime";
|
import NoteTime from "Element/NoteTime";
|
||||||
import Reveal from "Element/Reveal";
|
import Reveal from "Element/Reveal";
|
||||||
import useModeration from "Hooks/useModeration";
|
import useModeration from "Hooks/useModeration";
|
||||||
import { UserCache } from "Cache/UserCache";
|
import { UserCache } from "Cache/UserCache";
|
||||||
import Poll from "Element/Poll";
|
import Poll from "Element/Poll";
|
||||||
import { EventExt } from "System/EventExt";
|
|
||||||
import useLogin from "Hooks/useLogin";
|
import useLogin from "Hooks/useLogin";
|
||||||
import { setBookmarked, setPinned } from "Login";
|
import { setBookmarked, setPinned } from "Login";
|
||||||
import { NostrFileElement } from "Element/NostrFileHeader";
|
import { NostrFileElement } from "Element/NostrFileHeader";
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import "./NoteCreator.css";
|
import "./NoteCreator.css";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { encodeTLV, EventKind, NostrPrefix, TaggedRawEvent } from "@snort/nostr";
|
import { encodeTLV, EventKind, NostrPrefix, TaggedRawEvent, EventBuilder } from "@snort/system";
|
||||||
|
|
||||||
import Icon from "Icons/Icon";
|
import Icon from "Icons/Icon";
|
||||||
import useEventPublisher from "Feed/EventPublisher";
|
import useEventPublisher from "Feed/EventPublisher";
|
||||||
import { openFile } from "Util";
|
import { openFile } from "SnortUtils";
|
||||||
import Textarea from "Element/Textarea";
|
import Textarea from "Element/Textarea";
|
||||||
import Modal from "Element/Modal";
|
import Modal from "Element/Modal";
|
||||||
import ProfileImage from "Element/ProfileImage";
|
import ProfileImage from "Element/ProfileImage";
|
||||||
@ -31,7 +31,6 @@ import { LNURL } from "LNURL";
|
|||||||
import messages from "./messages";
|
import messages from "./messages";
|
||||||
import { ClipboardEventHandler, useState } from "react";
|
import { ClipboardEventHandler, useState } from "react";
|
||||||
import Spinner from "Icons/Spinner";
|
import Spinner from "Icons/Spinner";
|
||||||
import { EventBuilder } from "System";
|
|
||||||
import { Menu, MenuItem } from "@szhsin/react-menu";
|
import { Menu, MenuItem } from "@szhsin/react-menu";
|
||||||
import { LoginStore } from "Login";
|
import { LoginStore } from "Login";
|
||||||
import { getCurrentSubscription } from "Subscription";
|
import { getCurrentSubscription } from "Subscription";
|
||||||
|
@ -3,14 +3,14 @@ import { useSelector, useDispatch } from "react-redux";
|
|||||||
import { useIntl, FormattedMessage } from "react-intl";
|
import { useIntl, FormattedMessage } from "react-intl";
|
||||||
import { Menu, MenuItem } from "@szhsin/react-menu";
|
import { Menu, MenuItem } from "@szhsin/react-menu";
|
||||||
import { useLongPress } from "use-long-press";
|
import { useLongPress } from "use-long-press";
|
||||||
import { TaggedRawEvent, HexKey, u256, encodeTLV, NostrPrefix, Lists } from "@snort/nostr";
|
import { TaggedRawEvent, HexKey, u256, encodeTLV, NostrPrefix, Lists } from "@snort/system";
|
||||||
|
|
||||||
import Icon from "Icons/Icon";
|
import Icon from "Icons/Icon";
|
||||||
import Spinner from "Icons/Spinner";
|
import Spinner from "Icons/Spinner";
|
||||||
|
|
||||||
import { formatShort } from "Number";
|
import { formatShort } from "Number";
|
||||||
import useEventPublisher from "Feed/EventPublisher";
|
import useEventPublisher from "Feed/EventPublisher";
|
||||||
import { delay, normalizeReaction, unwrap } from "Util";
|
import { delay, normalizeReaction, unwrap } from "SnortUtils";
|
||||||
import { NoteCreator } from "Element/NoteCreator";
|
import { NoteCreator } from "Element/NoteCreator";
|
||||||
import { ReBroadcaster } from "Element/ReBroadcaster";
|
import { ReBroadcaster } from "Element/ReBroadcaster";
|
||||||
import Reactions from "Element/Reactions";
|
import Reactions from "Element/Reactions";
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import useEventFeed from "Feed/EventFeed";
|
import useEventFeed from "Feed/EventFeed";
|
||||||
import { NostrLink } from "Util";
|
import { NostrLink } from "@snort/system";
|
||||||
import Note from "Element/Note";
|
import Note from "Element/Note";
|
||||||
import PageSpinner from "Element/PageSpinner";
|
import PageSpinner from "Element/PageSpinner";
|
||||||
|
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
import "./NoteReaction.css";
|
import "./NoteReaction.css";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { EventKind, RawEvent, TaggedRawEvent, NostrPrefix } from "@snort/nostr";
|
import { EventKind, NostrEvent, TaggedRawEvent, NostrPrefix, EventExt } from "@snort/system";
|
||||||
|
|
||||||
import Note from "Element/Note";
|
import Note from "Element/Note";
|
||||||
import ProfileImage from "Element/ProfileImage";
|
import ProfileImage from "Element/ProfileImage";
|
||||||
import { eventLink, hexToBech32 } from "Util";
|
import { eventLink, hexToBech32 } from "SnortUtils";
|
||||||
import NoteTime from "Element/NoteTime";
|
import NoteTime from "Element/NoteTime";
|
||||||
import useModeration from "Hooks/useModeration";
|
import useModeration from "Hooks/useModeration";
|
||||||
import { EventExt } from "System/EventExt";
|
|
||||||
|
|
||||||
export interface NoteReactionProps {
|
export interface NoteReactionProps {
|
||||||
data: TaggedRawEvent;
|
data: TaggedRawEvent;
|
||||||
@ -43,7 +42,7 @@ export default function NoteReaction(props: NoteReactionProps) {
|
|||||||
function extractRoot() {
|
function extractRoot() {
|
||||||
if (ev?.kind === EventKind.Repost && ev.content.length > 0 && ev.content !== "#[0]") {
|
if (ev?.kind === EventKind.Repost && ev.content.length > 0 && ev.content !== "#[0]") {
|
||||||
try {
|
try {
|
||||||
const r: RawEvent = JSON.parse(ev.content);
|
const r: NostrEvent = JSON.parse(ev.content);
|
||||||
return r as TaggedRawEvent;
|
return r as TaggedRawEvent;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Could not load reposted content", e);
|
console.error("Could not load reposted content", e);
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import "./NoteToSelf.css";
|
import "./NoteToSelf.css";
|
||||||
import { Link, useNavigate } from "react-router-dom";
|
import { Link, useNavigate } from "react-router-dom";
|
||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
import { profileLink } from "Util";
|
import { profileLink } from "SnortUtils";
|
||||||
|
|
||||||
import messages from "./messages";
|
import messages from "./messages";
|
||||||
import Icon from "Icons/Icon";
|
import Icon from "Icons/Icon";
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { TaggedRawEvent } from "@snort/nostr";
|
import { TaggedRawEvent } from "@snort/system";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { FormattedMessage, FormattedNumber, useIntl } from "react-intl";
|
import { FormattedMessage, FormattedNumber, useIntl } from "react-intl";
|
||||||
|
|
||||||
@ -8,7 +8,7 @@ import useEventPublisher from "Feed/EventPublisher";
|
|||||||
import { useWallet } from "Wallet";
|
import { useWallet } from "Wallet";
|
||||||
import { useUserProfile } from "Hooks/useUserProfile";
|
import { useUserProfile } from "Hooks/useUserProfile";
|
||||||
import { LNURL } from "LNURL";
|
import { LNURL } from "LNURL";
|
||||||
import { unwrap } from "Util";
|
import { unwrap } from "SnortUtils";
|
||||||
import { formatShort } from "Number";
|
import { formatShort } from "Number";
|
||||||
import Spinner from "Icons/Spinner";
|
import Spinner from "Icons/Spinner";
|
||||||
import SendSats from "Element/SendSats";
|
import SendSats from "Element/SendSats";
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
import "./ProfileImage.css";
|
import "./ProfileImage.css";
|
||||||
|
|
||||||
import React, { useMemo } from "react";
|
import React, { useMemo } from "react";
|
||||||
import { HexKey, NostrPrefix } from "@snort/nostr";
|
import { HexKey, NostrPrefix, MetadataCache } from "@snort/system";
|
||||||
|
|
||||||
import { useUserProfile } from "Hooks/useUserProfile";
|
import { useUserProfile } from "Hooks/useUserProfile";
|
||||||
import { hexToBech32, profileLink } from "Util";
|
import { hexToBech32, profileLink } from "SnortUtils";
|
||||||
import Avatar from "Element/Avatar";
|
import Avatar from "Element/Avatar";
|
||||||
import Nip05 from "Element/Nip05";
|
import Nip05 from "Element/Nip05";
|
||||||
import { MetadataCache } from "Cache";
|
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
export interface ProfileImageProps {
|
export interface ProfileImageProps {
|
||||||
|
@ -4,7 +4,7 @@ import { ReactNode } from "react";
|
|||||||
import ProfileImage from "Element/ProfileImage";
|
import ProfileImage from "Element/ProfileImage";
|
||||||
import FollowButton from "Element/FollowButton";
|
import FollowButton from "Element/FollowButton";
|
||||||
import { useUserProfile } from "Hooks/useUserProfile";
|
import { useUserProfile } from "Hooks/useUserProfile";
|
||||||
import { HexKey } from "@snort/nostr";
|
import { HexKey } from "@snort/system";
|
||||||
import { useInView } from "react-intersection-observer";
|
import { useInView } from "react-intersection-observer";
|
||||||
|
|
||||||
export interface ProfilePreviewProps {
|
export interface ProfilePreviewProps {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import useImgProxy from "Hooks/useImgProxy";
|
import useImgProxy from "Hooks/useImgProxy";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
import { getUrlHostname } from "Util";
|
import { getUrlHostname } from "SnortUtils";
|
||||||
|
|
||||||
interface ProxyImgProps extends React.DetailedHTMLProps<React.ImgHTMLAttributes<HTMLImageElement>, HTMLImageElement> {
|
interface ProxyImgProps extends React.DetailedHTMLProps<React.ImgHTMLAttributes<HTMLImageElement>, HTMLImageElement> {
|
||||||
size?: number;
|
size?: number;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { RawEvent } from "@snort/nostr";
|
import { NostrEvent } from "@snort/system";
|
||||||
import { FormattedMessage, FormattedNumber } from "react-intl";
|
import { FormattedMessage, FormattedNumber } from "react-intl";
|
||||||
|
|
||||||
import { dedupe, hexToBech32, unixNow } from "Util";
|
import { dedupe, hexToBech32, unixNow } from "SnortUtils";
|
||||||
import FollowListBase from "Element/FollowListBase";
|
import FollowListBase from "Element/FollowListBase";
|
||||||
import AsyncButton from "Element/AsyncButton";
|
import AsyncButton from "Element/AsyncButton";
|
||||||
import { useWallet } from "Wallet";
|
import { useWallet } from "Wallet";
|
||||||
@ -13,7 +13,7 @@ import { LNURL } from "LNURL";
|
|||||||
import useEventPublisher from "Feed/EventPublisher";
|
import useEventPublisher from "Feed/EventPublisher";
|
||||||
import { WalletInvoiceState } from "Wallet";
|
import { WalletInvoiceState } from "Wallet";
|
||||||
|
|
||||||
export default function PubkeyList({ ev, className }: { ev: RawEvent; className?: string }) {
|
export default function PubkeyList({ ev, className }: { ev: NostrEvent; className?: string }) {
|
||||||
const wallet = useWallet();
|
const wallet = useWallet();
|
||||||
const login = useLogin();
|
const login = useLogin();
|
||||||
const publisher = useEventPublisher();
|
const publisher = useEventPublisher();
|
||||||
|
@ -2,7 +2,7 @@ import "./Reactions.css";
|
|||||||
|
|
||||||
import { useState, useMemo, useEffect } from "react";
|
import { useState, useMemo, useEffect } from "react";
|
||||||
import { useIntl, FormattedMessage } from "react-intl";
|
import { useIntl, FormattedMessage } from "react-intl";
|
||||||
import { TaggedRawEvent } from "@snort/nostr";
|
import { TaggedRawEvent } from "@snort/system";
|
||||||
|
|
||||||
import { formatShort } from "Number";
|
import { formatShort } from "Number";
|
||||||
import Icon from "Icons/Icon";
|
import Icon from "Icons/Icon";
|
||||||
|
@ -2,11 +2,11 @@ import "./Relay.css";
|
|||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { RelaySettings } from "@snort/nostr";
|
import { RelaySettings } from "@snort/system";
|
||||||
|
|
||||||
import useRelayState from "Feed/RelayState";
|
import useRelayState from "Feed/RelayState";
|
||||||
import { System } from "System";
|
import { System } from "index";
|
||||||
import { getRelayName, unixNowMs, unwrap } from "Util";
|
import { getRelayName, unixNowMs, unwrap } from "SnortUtils";
|
||||||
import useLogin from "Hooks/useLogin";
|
import useLogin from "Hooks/useLogin";
|
||||||
import { setRelays } from "Login";
|
import { setRelays } from "Login";
|
||||||
import Icon from "Icons/Icon";
|
import Icon from "Icons/Icon";
|
||||||
@ -20,7 +20,9 @@ export interface RelayProps {
|
|||||||
export default function Relay(props: RelayProps) {
|
export default function Relay(props: RelayProps) {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const login = useLogin();
|
const login = useLogin();
|
||||||
const relaySettings = unwrap(login.relays.item[props.addr] ?? System.Sockets.get(props.addr)?.Settings ?? {});
|
const relaySettings = unwrap(
|
||||||
|
login.relays.item[props.addr] ?? System.Sockets.find(a => a.address === props.addr)?.settings ?? {}
|
||||||
|
);
|
||||||
const state = useRelayState(props.addr);
|
const state = useRelayState(props.addr);
|
||||||
const name = useMemo(() => getRelayName(props.addr), [props.addr]);
|
const name = useMemo(() => getRelayName(props.addr), [props.addr]);
|
||||||
|
|
||||||
@ -35,7 +37,6 @@ export default function Relay(props: RelayProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const latency = Math.floor(state?.avgLatency ?? 0);
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={`relay w-max`}>
|
<div className={`relay w-max`}>
|
||||||
|
@ -2,7 +2,7 @@ import "./RelaysMetadata.css";
|
|||||||
import Nostrich from "nostrich.webp";
|
import Nostrich from "nostrich.webp";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
import { FullRelaySettings } from "@snort/nostr";
|
import { FullRelaySettings } from "@snort/system";
|
||||||
import Icon from "Icons/Icon";
|
import Icon from "Icons/Icon";
|
||||||
|
|
||||||
const RelayFavicon = ({ url }: { url: string }) => {
|
const RelayFavicon = ({ url }: { url: string }) => {
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import "./SendSats.css";
|
import "./SendSats.css";
|
||||||
import React, { useEffect, useMemo, useState } from "react";
|
import React, { useEffect, useMemo, useState } from "react";
|
||||||
import { useIntl, FormattedMessage } from "react-intl";
|
import { useIntl, FormattedMessage } from "react-intl";
|
||||||
import { HexKey, RawEvent } from "@snort/nostr";
|
|
||||||
|
|
||||||
|
import { HexKey, NostrEvent, EventPublisher } from "@snort/system";
|
||||||
|
import { System } from "index";
|
||||||
import { formatShort } from "Number";
|
import { formatShort } from "Number";
|
||||||
import Icon from "Icons/Icon";
|
import Icon from "Icons/Icon";
|
||||||
import useEventPublisher from "Feed/EventPublisher";
|
import useEventPublisher from "Feed/EventPublisher";
|
||||||
@ -11,11 +12,10 @@ import Modal from "Element/Modal";
|
|||||||
import QrCode from "Element/QrCode";
|
import QrCode from "Element/QrCode";
|
||||||
import Copy from "Element/Copy";
|
import Copy from "Element/Copy";
|
||||||
import { LNURL, LNURLError, LNURLErrorCode, LNURLInvoice, LNURLSuccessAction } from "LNURL";
|
import { LNURL, LNURLError, LNURLErrorCode, LNURLInvoice, LNURLSuccessAction } from "LNURL";
|
||||||
import { chunks, debounce } from "Util";
|
import { chunks, debounce } from "SnortUtils";
|
||||||
import { useWallet } from "Wallet";
|
import { useWallet } from "Wallet";
|
||||||
import useLogin from "Hooks/useLogin";
|
import useLogin from "Hooks/useLogin";
|
||||||
import { generateRandomKey } from "Login";
|
import { generateRandomKey } from "Login";
|
||||||
import { EventPublisher } from "System/EventPublisher";
|
|
||||||
import { ZapPoolController } from "ZapPoolController";
|
import { ZapPoolController } from "ZapPoolController";
|
||||||
|
|
||||||
import messages from "./messages";
|
import messages from "./messages";
|
||||||
@ -124,7 +124,7 @@ export default function SendSats(props: SendSatsProps) {
|
|||||||
async function loadInvoice() {
|
async function loadInvoice() {
|
||||||
if (!amount || !handler || !publisher) return null;
|
if (!amount || !handler || !publisher) return null;
|
||||||
|
|
||||||
let zap: RawEvent | undefined;
|
let zap: NostrEvent | undefined;
|
||||||
if (author && zapType !== ZapType.NonZap) {
|
if (author && zapType !== ZapType.NonZap) {
|
||||||
const relays = Object.keys(login.relays.item);
|
const relays = Object.keys(login.relays.item);
|
||||||
|
|
||||||
@ -133,7 +133,7 @@ export default function SendSats(props: SendSatsProps) {
|
|||||||
const randomKey = generateRandomKey();
|
const randomKey = generateRandomKey();
|
||||||
console.debug("Generated new key for zap: ", randomKey);
|
console.debug("Generated new key for zap: ", randomKey);
|
||||||
|
|
||||||
const publisher = new EventPublisher(randomKey.publicKey, randomKey.privateKey);
|
const publisher = new EventPublisher(System, randomKey.publicKey, randomKey.privateKey);
|
||||||
zap = await publisher.zap(amount * 1000, author, relays, note, comment, eb => eb.tag(["anon", ""]));
|
zap = await publisher.zap(amount * 1000, author, relays, note, comment, eb => eb.tag(["anon", ""]));
|
||||||
} else {
|
} else {
|
||||||
zap = await publisher.zap(amount * 1000, author, relays, note, comment);
|
zap = await publisher.zap(amount * 1000, author, relays, note, comment);
|
||||||
|
@ -3,11 +3,11 @@ import { useState } from "react";
|
|||||||
|
|
||||||
import useRelayState from "Feed/RelayState";
|
import useRelayState from "Feed/RelayState";
|
||||||
import Tabs, { Tab } from "Element/Tabs";
|
import Tabs, { Tab } from "Element/Tabs";
|
||||||
import { System } from "System";
|
import { unwrap } from "SnortUtils";
|
||||||
import { unwrap } from "Util";
|
|
||||||
import useSystemState from "Hooks/useSystemState";
|
import useSystemState from "Hooks/useSystemState";
|
||||||
import { RawReqFilter } from "@snort/nostr";
|
import { ReqFilter } from "@snort/system";
|
||||||
import { useCopy } from "useCopy";
|
import { useCopy } from "useCopy";
|
||||||
|
import { System } from "index";
|
||||||
|
|
||||||
function RelayInfo({ id }: { id: string }) {
|
function RelayInfo({ id }: { id: string }) {
|
||||||
const state = useRelayState(id);
|
const state = useRelayState(id);
|
||||||
@ -18,7 +18,7 @@ function Queries() {
|
|||||||
const qs = useSystemState();
|
const qs = useSystemState();
|
||||||
const { copy } = useCopy();
|
const { copy } = useCopy();
|
||||||
|
|
||||||
function countElements(filters: Array<RawReqFilter>) {
|
function countElements(filters: Array<ReqFilter>) {
|
||||||
let total = 0;
|
let total = 0;
|
||||||
for (const f of filters) {
|
for (const f of filters) {
|
||||||
for (const v of Object.values(f)) {
|
for (const v of Object.values(f)) {
|
||||||
@ -30,15 +30,10 @@ function Queries() {
|
|||||||
return total;
|
return total;
|
||||||
}
|
}
|
||||||
|
|
||||||
function queryInfo(q: {
|
function queryInfo(q: { id: string; filters: Array<ReqFilter>; subFilters: Array<ReqFilter> }) {
|
||||||
id: string;
|
|
||||||
filters: Array<RawReqFilter>;
|
|
||||||
closing: boolean;
|
|
||||||
subFilters: Array<RawReqFilter>;
|
|
||||||
}) {
|
|
||||||
return (
|
return (
|
||||||
<div key={q.id}>
|
<div key={q.id}>
|
||||||
{q.closing ? <s>{q.id}</s> : <>{q.id}</>}
|
{q.id}
|
||||||
<br />
|
<br />
|
||||||
<span onClick={() => copy(JSON.stringify(q.filters))} className="pointer">
|
<span onClick={() => copy(JSON.stringify(q.filters))} className="pointer">
|
||||||
Filters: {q.filters.length} ({countElements(q.filters)} elements)
|
Filters: {q.filters.length} ({countElements(q.filters)} elements)
|
||||||
@ -66,8 +61,8 @@ const SubDebug = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<b>Connections:</b>
|
<b>Connections:</b>
|
||||||
{[...System.Sockets.keys()].map(k => (
|
{System.Sockets.map(k => (
|
||||||
<RelayInfo id={k} />
|
<RelayInfo id={k.address} />
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { HexKey, NostrPrefix } from "@snort/nostr";
|
import { HexKey, NostrPrefix } from "@snort/system";
|
||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
|
|
||||||
import FollowListBase from "Element/FollowListBase";
|
import FollowListBase from "Element/FollowListBase";
|
||||||
@ -7,7 +7,7 @@ import PageSpinner from "Element/PageSpinner";
|
|||||||
import NostrBandApi from "External/NostrBand";
|
import NostrBandApi from "External/NostrBand";
|
||||||
import SemisolDevApi from "External/SemisolDev";
|
import SemisolDevApi from "External/SemisolDev";
|
||||||
import useLogin from "Hooks/useLogin";
|
import useLogin from "Hooks/useLogin";
|
||||||
import { hexToBech32 } from "Util";
|
import { hexToBech32 } from "SnortUtils";
|
||||||
|
|
||||||
enum Provider {
|
enum Provider {
|
||||||
NostrBand = 1,
|
NostrBand = 1,
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import "./Text.css";
|
import "./Text.css";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { Link, useLocation } from "react-router-dom";
|
import { Link, useLocation } from "react-router-dom";
|
||||||
import { HexKey, NostrPrefix } from "@snort/nostr";
|
import { HexKey, NostrPrefix, validateNostrLink } from "@snort/system";
|
||||||
|
|
||||||
import { MentionRegex, InvoiceRegex, HashtagRegex, CashuRegex } from "Const";
|
import { MentionRegex, InvoiceRegex, HashtagRegex, CashuRegex } from "Const";
|
||||||
import { eventLink, hexToBech32, splitByUrl, validateNostrLink } from "Util";
|
import { eventLink, hexToBech32, splitByUrl } from "SnortUtils";
|
||||||
import Invoice from "Element/Invoice";
|
import Invoice from "Element/Invoice";
|
||||||
import Hashtag from "Element/Hashtag";
|
import Hashtag from "Element/Hashtag";
|
||||||
import Mention from "Element/Mention";
|
import Mention from "Element/Mention";
|
||||||
|
@ -4,12 +4,11 @@ import "./Textarea.css";
|
|||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
import ReactTextareaAutocomplete from "@webscopeio/react-textarea-autocomplete";
|
import ReactTextareaAutocomplete from "@webscopeio/react-textarea-autocomplete";
|
||||||
import TextareaAutosize from "react-textarea-autosize";
|
import TextareaAutosize from "react-textarea-autosize";
|
||||||
import { NostrPrefix } from "@snort/nostr";
|
import { NostrPrefix, MetadataCache } from "@snort/system";
|
||||||
|
|
||||||
import Avatar from "Element/Avatar";
|
import Avatar from "Element/Avatar";
|
||||||
import Nip05 from "Element/Nip05";
|
import Nip05 from "Element/Nip05";
|
||||||
import { hexToBech32 } from "Util";
|
import { hexToBech32 } from "SnortUtils";
|
||||||
import { MetadataCache } from "Cache";
|
|
||||||
import { UserCache } from "Cache/UserCache";
|
import { UserCache } from "Cache/UserCache";
|
||||||
|
|
||||||
import messages from "./messages";
|
import messages from "./messages";
|
||||||
|
@ -2,10 +2,17 @@ import "./Thread.css";
|
|||||||
import { useMemo, useState, ReactNode } from "react";
|
import { useMemo, useState, ReactNode } from "react";
|
||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
import { useNavigate, useLocation, Link, useParams } from "react-router-dom";
|
import { useNavigate, useLocation, Link, useParams } from "react-router-dom";
|
||||||
import { TaggedRawEvent, u256, EventKind, NostrPrefix } from "@snort/nostr";
|
import {
|
||||||
import { EventExt, Thread as ThreadInfo } from "System/EventExt";
|
TaggedRawEvent,
|
||||||
|
u256,
|
||||||
|
EventKind,
|
||||||
|
NostrPrefix,
|
||||||
|
EventExt,
|
||||||
|
Thread as ThreadInfo,
|
||||||
|
parseNostrLink,
|
||||||
|
} from "@snort/system";
|
||||||
|
|
||||||
import { eventLink, unwrap, getReactions, parseNostrLink, getAllReactions, findTag } from "Util";
|
import { eventLink, unwrap, getReactions, getAllReactions, findTag } from "SnortUtils";
|
||||||
import BackButton from "Element/BackButton";
|
import BackButton from "Element/BackButton";
|
||||||
import Note from "Element/Note";
|
import Note from "Element/Note";
|
||||||
import NoteGhost from "Element/NoteGhost";
|
import NoteGhost from "Element/NoteGhost";
|
||||||
|
@ -2,10 +2,10 @@ import "./Timeline.css";
|
|||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
import { useCallback, useMemo } from "react";
|
import { useCallback, useMemo } from "react";
|
||||||
import { useInView } from "react-intersection-observer";
|
import { useInView } from "react-intersection-observer";
|
||||||
import { TaggedRawEvent, EventKind, u256 } from "@snort/nostr";
|
import { TaggedRawEvent, EventKind, u256 } from "@snort/system";
|
||||||
|
|
||||||
import Icon from "Icons/Icon";
|
import Icon from "Icons/Icon";
|
||||||
import { dedupeByPubkey, findTag, tagFilterOfTextRepost } from "Util";
|
import { dedupeByPubkey, findTag, tagFilterOfTextRepost } from "SnortUtils";
|
||||||
import ProfileImage from "Element/ProfileImage";
|
import ProfileImage from "Element/ProfileImage";
|
||||||
import useTimelineFeed, { TimelineFeed, TimelineSubject } from "Feed/TimelineFeed";
|
import useTimelineFeed, { TimelineFeed, TimelineSubject } from "Feed/TimelineFeed";
|
||||||
import LoadMore from "Element/LoadMore";
|
import LoadMore from "Element/LoadMore";
|
||||||
@ -143,7 +143,7 @@ const Timeline = (props: TimelineProps) => {
|
|||||||
)}
|
)}
|
||||||
{mainFeed.map(eventElement)}
|
{mainFeed.map(eventElement)}
|
||||||
{(props.loadMore === undefined || props.loadMore === true) && (
|
{(props.loadMore === undefined || props.loadMore === true) && (
|
||||||
<LoadMore onLoadMore={feed.loadMore} shouldLoadMore={!feed.loading}>
|
<LoadMore onLoadMore={() => feed.loadMore()} shouldLoadMore={!feed.loading}>
|
||||||
<Skeleton width="100%" height="120px" margin="0 0 16px 0" />
|
<Skeleton width="100%" height="120px" margin="0 0 16px 0" />
|
||||||
<Skeleton width="100%" height="120px" margin="0 0 16px 0" />
|
<Skeleton width="100%" height="120px" margin="0 0 16px 0" />
|
||||||
<Skeleton width="100%" height="120px" margin="0 0 16px 0" />
|
<Skeleton width="100%" height="120px" margin="0 0 16px 0" />
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { RawEvent, TaggedRawEvent } from "@snort/nostr";
|
import { NostrEvent, TaggedRawEvent } from "@snort/system";
|
||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
|
|
||||||
import PageSpinner from "Element/PageSpinner";
|
import PageSpinner from "Element/PageSpinner";
|
||||||
@ -7,7 +7,7 @@ import Note from "Element/Note";
|
|||||||
import NostrBandApi from "External/NostrBand";
|
import NostrBandApi from "External/NostrBand";
|
||||||
|
|
||||||
export default function TrendingNotes() {
|
export default function TrendingNotes() {
|
||||||
const [posts, setPosts] = useState<Array<RawEvent>>();
|
const [posts, setPosts] = useState<Array<NostrEvent>>();
|
||||||
|
|
||||||
async function loadTrendingNotes() {
|
async function loadTrendingNotes() {
|
||||||
const api = new NostrBandApi();
|
const api = new NostrBandApi();
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { HexKey } from "@snort/nostr";
|
import { HexKey } from "@snort/system";
|
||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
|
|
||||||
import FollowListBase from "Element/FollowListBase";
|
import FollowListBase from "Element/FollowListBase";
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { MouseEvent } from "react";
|
import { MouseEvent } from "react";
|
||||||
import { useNavigate, Link } from "react-router-dom";
|
import { useNavigate, Link } from "react-router-dom";
|
||||||
|
|
||||||
import { HexKey } from "@snort/nostr";
|
import { HexKey } from "@snort/system";
|
||||||
|
|
||||||
import { useUserProfile } from "Hooks/useUserProfile";
|
import { useUserProfile } from "Hooks/useUserProfile";
|
||||||
import { profileLink } from "Util";
|
import { profileLink } from "SnortUtils";
|
||||||
|
|
||||||
export default function Username({ pubkey, onLinkVisit }: { pubkey: HexKey; onLinkVisit(): void }) {
|
export default function Username({ pubkey, onLinkVisit }: { pubkey: HexKey; onLinkVisit(): void }) {
|
||||||
const user = useUserProfile(pubkey);
|
const user = useUserProfile(pubkey);
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
import { encodeTLV, NostrPrefix, RawEvent } from "@snort/nostr";
|
import { encodeTLV, NostrPrefix, NostrEvent } from "@snort/system";
|
||||||
import useEventPublisher from "Feed/EventPublisher";
|
import useEventPublisher from "Feed/EventPublisher";
|
||||||
import Icon from "Icons/Icon";
|
import Icon from "Icons/Icon";
|
||||||
import Spinner from "Icons/Spinner";
|
import Spinner from "Icons/Spinner";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import useFileUpload from "Upload";
|
import useFileUpload from "Upload";
|
||||||
import { openFile } from "Util";
|
import { openFile } from "SnortUtils";
|
||||||
import Textarea from "./Textarea";
|
import Textarea from "./Textarea";
|
||||||
|
|
||||||
export default function WriteDm({ chatPubKey }: { chatPubKey: string }) {
|
export default function WriteDm({ chatPubKey }: { chatPubKey: string }) {
|
||||||
const [msg, setMsg] = useState("");
|
const [msg, setMsg] = useState("");
|
||||||
const [sending, setSending] = useState(false);
|
const [sending, setSending] = useState(false);
|
||||||
const [uploading, setUploading] = useState(false);
|
const [uploading, setUploading] = useState(false);
|
||||||
const [otherEvents, setOtherEvents] = useState<Array<RawEvent>>([]);
|
const [otherEvents, setOtherEvents] = useState<Array<NostrEvent>>([]);
|
||||||
const [error, setError] = useState("");
|
const [error, setError] = useState("");
|
||||||
const publisher = useEventPublisher();
|
const publisher = useEventPublisher();
|
||||||
const uploader = useFileUpload();
|
const uploader = useFileUpload();
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import "./Zap.css";
|
import "./Zap.css";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
import { HexKey, TaggedRawEvent } from "@snort/nostr";
|
import { HexKey, TaggedRawEvent } from "@snort/system";
|
||||||
|
|
||||||
import { decodeInvoice, InvoiceDetails, sha256, unwrap } from "Util";
|
import { decodeInvoice, InvoiceDetails, sha256, unwrap } from "SnortUtils";
|
||||||
import { formatShort } from "Number";
|
import { formatShort } from "Number";
|
||||||
import Text from "Element/Text";
|
import Text from "Element/Text";
|
||||||
import ProfileImage from "Element/ProfileImage";
|
import ProfileImage from "Element/ProfileImage";
|
||||||
import { findTag } from "Util";
|
import { findTag } from "SnortUtils";
|
||||||
import { UserCache } from "Cache/UserCache";
|
import { UserCache } from "Cache/UserCache";
|
||||||
import useLogin from "Hooks/useLogin";
|
import useLogin from "Hooks/useLogin";
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import "./ZapButton.css";
|
import "./ZapButton.css";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { HexKey } from "@snort/nostr";
|
import { HexKey } from "@snort/system";
|
||||||
|
|
||||||
import { useUserProfile } from "Hooks/useUserProfile";
|
import { useUserProfile } from "Hooks/useUserProfile";
|
||||||
import SendSats from "Element/SendSats";
|
import SendSats from "Element/SendSats";
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import "./ZapstrEmbed.css";
|
import "./ZapstrEmbed.css";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { encodeTLV, NostrPrefix, RawEvent } from "@snort/nostr";
|
import { encodeTLV, NostrPrefix, NostrEvent } from "@snort/system";
|
||||||
|
|
||||||
import { ProxyImg } from "Element/ProxyImg";
|
import { ProxyImg } from "Element/ProxyImg";
|
||||||
import ProfileImage from "Element/ProfileImage";
|
import ProfileImage from "Element/ProfileImage";
|
||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
|
|
||||||
export default function ZapstrEmbed({ ev }: { ev: RawEvent }) {
|
export default function ZapstrEmbed({ ev }: { ev: NostrEvent }) {
|
||||||
const media = ev.tags.find(a => a[0] === "media");
|
const media = ev.tags.find(a => a[0] === "media");
|
||||||
const cover = ev.tags.find(a => a[0] === "cover");
|
const cover = ev.tags.find(a => a[0] === "cover");
|
||||||
const subject = ev.tags.find(a => a[0] === "subject");
|
const subject = ev.tags.find(a => a[0] === "subject");
|
||||||
|
6
packages/app/src/External/NostrBand.ts
vendored
6
packages/app/src/External/NostrBand.ts
vendored
@ -1,4 +1,4 @@
|
|||||||
import { RawEvent } from "@snort/nostr";
|
import { NostrEvent } from "@snort/system";
|
||||||
|
|
||||||
export interface TrendingUser {
|
export interface TrendingUser {
|
||||||
pubkey: string;
|
pubkey: string;
|
||||||
@ -9,8 +9,8 @@ export interface TrendingUserResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface TrendingNote {
|
export interface TrendingNote {
|
||||||
event: RawEvent;
|
event: NostrEvent;
|
||||||
author: RawEvent; // kind0 event
|
author: NostrEvent; // kind0 event
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TrendingNoteResponse {
|
export interface TrendingNoteResponse {
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
type HookFn = () => void;
|
type HookFn<TSnapshot> = (e?: TSnapshot) => void;
|
||||||
|
|
||||||
interface HookFilter {
|
interface HookFilter<TSnapshot> {
|
||||||
fn: HookFn;
|
fn: HookFn<TSnapshot>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Simple React hookable store with manual change notifications
|
* Simple React hookable store with manual change notifications
|
||||||
*/
|
*/
|
||||||
export default abstract class ExternalStore<TSnapshot> {
|
export default abstract class ExternalStore<TSnapshot> {
|
||||||
#hooks: Array<HookFilter> = [];
|
#hooks: Array<HookFilter<TSnapshot>> = [];
|
||||||
#snapshot: Readonly<TSnapshot> = {} as Readonly<TSnapshot>;
|
#snapshot: Readonly<TSnapshot> = {} as Readonly<TSnapshot>;
|
||||||
#changed = true;
|
#changed = true;
|
||||||
|
|
||||||
hook(fn: HookFn) {
|
hook(fn: HookFn<TSnapshot>) {
|
||||||
this.#hooks.push({
|
this.#hooks.push({
|
||||||
fn,
|
fn,
|
||||||
});
|
});
|
||||||
@ -32,9 +32,9 @@ export default abstract class ExternalStore<TSnapshot> {
|
|||||||
return this.#snapshot;
|
return this.#snapshot;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected notifyChange() {
|
protected notifyChange(sn?: TSnapshot) {
|
||||||
this.#changed = true;
|
this.#changed = true;
|
||||||
this.#hooks.forEach(h => h.fn());
|
this.#hooks.forEach(h => h.fn(sn));
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract takeSnapshot(): TSnapshot;
|
abstract takeSnapshot(): TSnapshot;
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { EventKind, HexKey, Lists } from "@snort/nostr";
|
import { EventKind, HexKey, Lists, RequestBuilder, FlatNoteStore, ReplaceableNoteStore } from "@snort/system";
|
||||||
|
|
||||||
import { unwrap, findTag, chunks } from "Util";
|
import { unwrap, findTag, chunks } from "SnortUtils";
|
||||||
import { RequestBuilder } from "System";
|
|
||||||
import { FlatNoteStore, ReplaceableNoteStore } from "System/NoteCollection";
|
|
||||||
import useRequestBuilder from "Hooks/useRequestBuilder";
|
import useRequestBuilder from "Hooks/useRequestBuilder";
|
||||||
|
|
||||||
type BadgeAwards = {
|
type BadgeAwards = {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { HexKey, Lists } from "@snort/nostr";
|
import { HexKey, Lists } from "@snort/system";
|
||||||
|
|
||||||
import useNotelistSubscription from "Hooks/useNotelistSubscription";
|
import useNotelistSubscription from "Hooks/useNotelistSubscription";
|
||||||
import useLogin from "Hooks/useLogin";
|
import useLogin from "Hooks/useLogin";
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { NostrPrefix } from "@snort/nostr";
|
import { NostrPrefix, RequestBuilder, ReplaceableNoteStore, NostrLink } from "@snort/system";
|
||||||
|
|
||||||
import useRequestBuilder from "Hooks/useRequestBuilder";
|
import useRequestBuilder from "Hooks/useRequestBuilder";
|
||||||
import { RequestBuilder, ReplaceableNoteStore } from "System";
|
import { unwrap } from "SnortUtils";
|
||||||
import { NostrLink, unwrap } from "Util";
|
|
||||||
|
|
||||||
export default function useEventFeed(link: NostrLink) {
|
export default function useEventFeed(link: NostrLink) {
|
||||||
const sub = useMemo(() => {
|
const sub = useMemo(() => {
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import useLogin from "Hooks/useLogin";
|
import useLogin from "Hooks/useLogin";
|
||||||
import { EventPublisher } from "System/EventPublisher";
|
import { EventPublisher } from "@snort/system";
|
||||||
|
import { System } from "index";
|
||||||
|
|
||||||
export default function useEventPublisher() {
|
export default function useEventPublisher() {
|
||||||
const { publicKey, privateKey } = useLogin();
|
const { publicKey, privateKey } = useLogin();
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
if (publicKey) {
|
if (publicKey) {
|
||||||
return new EventPublisher(publicKey, privateKey);
|
return new EventPublisher(System, publicKey, privateKey);
|
||||||
}
|
}
|
||||||
}, [publicKey, privateKey]);
|
}, [publicKey, privateKey]);
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { HexKey, EventKind } from "@snort/nostr";
|
import { HexKey, EventKind, PubkeyReplaceableNoteStore, RequestBuilder } from "@snort/system";
|
||||||
|
|
||||||
import { PubkeyReplaceableNoteStore, RequestBuilder } from "System";
|
|
||||||
import useRequestBuilder from "Hooks/useRequestBuilder";
|
import useRequestBuilder from "Hooks/useRequestBuilder";
|
||||||
|
|
||||||
export default function useFollowersFeed(pubkey?: HexKey) {
|
export default function useFollowersFeed(pubkey?: HexKey) {
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { HexKey, TaggedRawEvent, EventKind } from "@snort/nostr";
|
import { HexKey, TaggedRawEvent, EventKind, PubkeyReplaceableNoteStore, RequestBuilder } from "@snort/system";
|
||||||
|
|
||||||
import { PubkeyReplaceableNoteStore, RequestBuilder } from "System";
|
|
||||||
import useRequestBuilder from "Hooks/useRequestBuilder";
|
import useRequestBuilder from "Hooks/useRequestBuilder";
|
||||||
import useLogin from "Hooks/useLogin";
|
import useLogin from "Hooks/useLogin";
|
||||||
|
|
||||||
|
@ -1,25 +1,27 @@
|
|||||||
import { useEffect, useMemo } from "react";
|
import { useEffect, useMemo } from "react";
|
||||||
import { TaggedRawEvent, Lists, EventKind } from "@snort/nostr";
|
import { TaggedRawEvent, Lists, EventKind, FlatNoteStore, RequestBuilder } from "@snort/system";
|
||||||
|
import debug from "debug";
|
||||||
|
|
||||||
import { bech32ToHex, getNewest, getNewestEventTagsByKey, unwrap } from "Util";
|
import { bech32ToHex, getNewest, getNewestEventTagsByKey, unwrap } from "SnortUtils";
|
||||||
import { makeNotification, sendNotification } from "Notifications";
|
import { makeNotification, sendNotification } from "Notifications";
|
||||||
import useEventPublisher from "Feed/EventPublisher";
|
import useEventPublisher from "Feed/EventPublisher";
|
||||||
import { getMutedKeys } from "Feed/MuteList";
|
import { getMutedKeys } from "Feed/MuteList";
|
||||||
import useModeration from "Hooks/useModeration";
|
import useModeration from "Hooks/useModeration";
|
||||||
import { FlatNoteStore, RequestBuilder } from "System";
|
|
||||||
import useRequestBuilder from "Hooks/useRequestBuilder";
|
import useRequestBuilder from "Hooks/useRequestBuilder";
|
||||||
import { DmCache } from "Cache";
|
import { DmCache } from "Cache";
|
||||||
import useLogin from "Hooks/useLogin";
|
import useLogin from "Hooks/useLogin";
|
||||||
import { addSubscription, setBlocked, setBookmarked, setFollows, setMuted, setPinned, setRelays, setTags } from "Login";
|
import { addSubscription, setBlocked, setBookmarked, setFollows, setMuted, setPinned, setRelays, setTags } from "Login";
|
||||||
import { SnortPubKey } from "Const";
|
import { SnortPubKey } from "Const";
|
||||||
import { SubscriptionEvent } from "Subscription";
|
import { SubscriptionEvent } from "Subscription";
|
||||||
|
import useRelaysFeedFollows from "./RelaysFeedFollows";
|
||||||
|
import { UserRelays } from "Cache/UserRelayCache";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Managed loading data for the current logged in user
|
* Managed loading data for the current logged in user
|
||||||
*/
|
*/
|
||||||
export default function useLoginFeed() {
|
export default function useLoginFeed() {
|
||||||
const login = useLogin();
|
const login = useLogin();
|
||||||
const { publicKey: pubKey, readNotifications } = login;
|
const { publicKey: pubKey, readNotifications, follows } = login;
|
||||||
const { isMuted } = useModeration();
|
const { isMuted } = useModeration();
|
||||||
const publisher = useEventPublisher();
|
const publisher = useEventPublisher();
|
||||||
|
|
||||||
@ -39,6 +41,7 @@ export default function useLoginFeed() {
|
|||||||
.limit(1);
|
.limit(1);
|
||||||
|
|
||||||
const dmSince = DmCache.newest();
|
const dmSince = DmCache.newest();
|
||||||
|
debug("LoginFeed")("Loading dms since %s", new Date(dmSince * 1000).toISOString());
|
||||||
b.withFilter().authors([pubKey]).kinds([EventKind.DirectMessage]).since(dmSince);
|
b.withFilter().authors([pubKey]).kinds([EventKind.DirectMessage]).since(dmSince);
|
||||||
b.withFilter().kinds([EventKind.DirectMessage]).tag("p", [pubKey]).since(dmSince);
|
b.withFilter().kinds([EventKind.DirectMessage]).tag("p", [pubKey]).since(dmSince);
|
||||||
return b;
|
return b;
|
||||||
@ -171,8 +174,12 @@ export default function useLoginFeed() {
|
|||||||
}
|
}
|
||||||
}, [listsFeed]);
|
}, [listsFeed]);
|
||||||
|
|
||||||
/*const fRelays = useRelaysFeedFollows(follows);
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
FollowsRelays.bulkSet(fRelays).catch(console.error);
|
UserRelays.buffer(follows.item).catch(console.error);
|
||||||
}, [dispatch, fRelays]);*/
|
}, [follows.item]);
|
||||||
|
|
||||||
|
const fRelays = useRelaysFeedFollows(follows.item);
|
||||||
|
useEffect(() => {
|
||||||
|
UserRelays.bulkSet(fRelays).catch(console.error);
|
||||||
|
}, [fRelays]);
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,14 @@
|
|||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { HexKey, TaggedRawEvent, Lists, EventKind } from "@snort/nostr";
|
import {
|
||||||
|
HexKey,
|
||||||
|
TaggedRawEvent,
|
||||||
|
Lists,
|
||||||
|
EventKind,
|
||||||
|
ParameterizedReplaceableNoteStore,
|
||||||
|
RequestBuilder,
|
||||||
|
} from "@snort/system";
|
||||||
|
|
||||||
import { getNewest } from "Util";
|
import { getNewest } from "SnortUtils";
|
||||||
import { ParameterizedReplaceableNoteStore, RequestBuilder } from "System";
|
|
||||||
import useRequestBuilder from "Hooks/useRequestBuilder";
|
import useRequestBuilder from "Hooks/useRequestBuilder";
|
||||||
import useLogin from "Hooks/useLogin";
|
import useLogin from "Hooks/useLogin";
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { HexKey, Lists } from "@snort/nostr";
|
import { HexKey, Lists } from "@snort/system";
|
||||||
import useNotelistSubscription from "Hooks/useNotelistSubscription";
|
import useNotelistSubscription from "Hooks/useNotelistSubscription";
|
||||||
import useLogin from "Hooks/useLogin";
|
import useLogin from "Hooks/useLogin";
|
||||||
|
|
||||||
|
@ -1,18 +1,6 @@
|
|||||||
import { useSyncExternalStore } from "react";
|
import { System } from "index";
|
||||||
import { StateSnapshot } from "@snort/nostr";
|
|
||||||
import { System } from "System";
|
|
||||||
|
|
||||||
const noop = () => {
|
|
||||||
return () => undefined;
|
|
||||||
};
|
|
||||||
const noopState = (): StateSnapshot | undefined => {
|
|
||||||
return undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function useRelayState(addr: string) {
|
export default function useRelayState(addr: string) {
|
||||||
const c = System.Sockets.get(addr);
|
const c = System.Sockets.find(a => a.address === addr);
|
||||||
return useSyncExternalStore<StateSnapshot | undefined>(
|
return c;
|
||||||
c?.StatusHook.bind(c) ?? noop,
|
|
||||||
c?.GetState.bind(c) ?? noopState
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { HexKey, FullRelaySettings, EventKind } from "@snort/nostr";
|
import { HexKey, FullRelaySettings, EventKind, RequestBuilder, ReplaceableNoteStore } from "@snort/system";
|
||||||
|
|
||||||
import { RequestBuilder } from "System";
|
|
||||||
import { ReplaceableNoteStore } from "System/NoteCollection";
|
|
||||||
import useRequestBuilder from "Hooks/useRequestBuilder";
|
import useRequestBuilder from "Hooks/useRequestBuilder";
|
||||||
|
|
||||||
export default function useRelaysFeed(pubkey?: HexKey) {
|
export default function useRelaysFeed(pubkey?: HexKey) {
|
||||||
|
@ -1,73 +1,88 @@
|
|||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { HexKey, FullRelaySettings, TaggedRawEvent, RelaySettings, EventKind } from "@snort/nostr";
|
import {
|
||||||
|
HexKey,
|
||||||
|
FullRelaySettings,
|
||||||
|
TaggedRawEvent,
|
||||||
|
RelaySettings,
|
||||||
|
EventKind,
|
||||||
|
PubkeyReplaceableNoteStore,
|
||||||
|
RequestBuilder,
|
||||||
|
} from "@snort/system";
|
||||||
|
import debug from "debug";
|
||||||
|
|
||||||
import { sanitizeRelayUrl } from "Util";
|
import { sanitizeRelayUrl } from "SnortUtils";
|
||||||
import { PubkeyReplaceableNoteStore, RequestBuilder } from "System";
|
|
||||||
import useRequestBuilder from "Hooks/useRequestBuilder";
|
import useRequestBuilder from "Hooks/useRequestBuilder";
|
||||||
|
import { UserRelays } from "Cache/UserRelayCache";
|
||||||
|
|
||||||
type UserRelayMap = Record<HexKey, Array<FullRelaySettings>>;
|
interface RelayList {
|
||||||
|
pubkey: string;
|
||||||
|
created_at: number;
|
||||||
|
relays: FullRelaySettings[];
|
||||||
|
}
|
||||||
|
|
||||||
export default function useRelaysFeedFollows(pubkeys: HexKey[]): UserRelayMap {
|
export default function useRelaysFeedFollows(pubkeys: HexKey[]): Array<RelayList> {
|
||||||
const sub = useMemo(() => {
|
const sub = useMemo(() => {
|
||||||
const b = new RequestBuilder(`relays:follows`);
|
const b = new RequestBuilder(`relays:follows`);
|
||||||
b.withFilter().authors(pubkeys).kinds([EventKind.Relays, EventKind.ContactList]);
|
const since = UserRelays.newest();
|
||||||
|
debug("LoginFeed")("Loading relay lists since %s", new Date(since * 1000).toISOString());
|
||||||
|
b.withFilter().authors(pubkeys).kinds([EventKind.Relays, EventKind.ContactList]).since(since);
|
||||||
return b;
|
return b;
|
||||||
}, [pubkeys]);
|
}, [pubkeys]);
|
||||||
|
|
||||||
function mapFromRelays(notes: Array<TaggedRawEvent>): UserRelayMap {
|
function mapFromRelays(notes: Array<TaggedRawEvent>): Array<RelayList> {
|
||||||
return Object.fromEntries(
|
return notes.map(ev => {
|
||||||
notes.map(ev => {
|
return {
|
||||||
return [
|
pubkey: ev.pubkey,
|
||||||
ev.pubkey,
|
created_at: ev.created_at,
|
||||||
ev.tags
|
relays: ev.tags
|
||||||
.map(a => {
|
.map(a => {
|
||||||
return {
|
return {
|
||||||
url: sanitizeRelayUrl(a[1]),
|
url: sanitizeRelayUrl(a[1]),
|
||||||
settings: {
|
settings: {
|
||||||
read: a[2] === "read" || a[2] === undefined,
|
read: a[2] === "read" || a[2] === undefined,
|
||||||
write: a[2] === "write" || a[2] === undefined,
|
write: a[2] === "write" || a[2] === undefined,
|
||||||
},
|
},
|
||||||
} as FullRelaySettings;
|
} as FullRelaySettings;
|
||||||
})
|
})
|
||||||
.filter(a => a.url !== undefined),
|
.filter(a => a.url !== undefined),
|
||||||
];
|
};
|
||||||
})
|
});
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapFromContactList(notes: Array<TaggedRawEvent>): UserRelayMap {
|
// instead of discarding the follow list we should also use it for follow graph
|
||||||
return Object.fromEntries(
|
function mapFromContactList(notes: Array<TaggedRawEvent>): Array<RelayList> {
|
||||||
notes.map(ev => {
|
return notes.map(ev => {
|
||||||
if (ev.content !== "" && ev.content !== "{}" && ev.content.startsWith("{") && ev.content.endsWith("}")) {
|
if (ev.content !== "" && ev.content !== "{}" && ev.content.startsWith("{") && ev.content.endsWith("}")) {
|
||||||
try {
|
try {
|
||||||
const relays: Record<string, RelaySettings> = JSON.parse(ev.content);
|
const relays: Record<string, RelaySettings> = JSON.parse(ev.content);
|
||||||
return [
|
return {
|
||||||
ev.pubkey,
|
pubkey: ev.pubkey,
|
||||||
Object.entries(relays)
|
created_at: ev.created_at,
|
||||||
.map(([k, v]) => {
|
relays: Object.entries(relays)
|
||||||
return {
|
.map(([k, v]) => {
|
||||||
url: sanitizeRelayUrl(k),
|
return {
|
||||||
settings: v,
|
url: sanitizeRelayUrl(k),
|
||||||
} as FullRelaySettings;
|
settings: v,
|
||||||
})
|
} as FullRelaySettings;
|
||||||
.filter(a => a.url !== undefined),
|
})
|
||||||
];
|
.filter(a => a.url !== undefined),
|
||||||
} catch {
|
};
|
||||||
// ignored
|
} catch {
|
||||||
}
|
// ignored
|
||||||
}
|
}
|
||||||
return [ev.pubkey, []];
|
}
|
||||||
})
|
return {
|
||||||
);
|
pubkey: ev.pubkey,
|
||||||
|
created_at: 0,
|
||||||
|
relays: [],
|
||||||
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const relays = useRequestBuilder<PubkeyReplaceableNoteStore>(PubkeyReplaceableNoteStore, sub);
|
const relays = useRequestBuilder<PubkeyReplaceableNoteStore>(PubkeyReplaceableNoteStore, sub);
|
||||||
const notesRelays = relays.data?.filter(a => a.kind === EventKind.Relays) ?? [];
|
const notesRelays = relays.data?.filter(a => a.kind === EventKind.Relays) ?? [];
|
||||||
const notesContactLists = relays.data?.filter(a => a.kind === EventKind.ContactList) ?? [];
|
const notesContactLists = relays.data?.filter(a => a.kind === EventKind.ContactList) ?? [];
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
return {
|
return [...mapFromContactList(notesContactLists), ...mapFromRelays(notesRelays)];
|
||||||
...mapFromContactList(notesContactLists),
|
|
||||||
...mapFromRelays(notesRelays),
|
|
||||||
} as UserRelayMap;
|
|
||||||
}, [relays]);
|
}, [relays]);
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,22 @@
|
|||||||
import { useEffect, useMemo, useState } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
import { u256, EventKind } from "@snort/nostr";
|
import { u256, EventKind, NostrLink, FlatNoteStore, RequestBuilder } from "@snort/system";
|
||||||
|
|
||||||
import { appendDedupe, NostrLink } from "Util";
|
import { appendDedupe } from "SnortUtils";
|
||||||
import { FlatNoteStore, RequestBuilder } from "System";
|
|
||||||
import useRequestBuilder from "Hooks/useRequestBuilder";
|
import useRequestBuilder from "Hooks/useRequestBuilder";
|
||||||
import useLogin from "Hooks/useLogin";
|
import useLogin from "Hooks/useLogin";
|
||||||
|
|
||||||
|
interface RelayTaggedEventId {
|
||||||
|
id: u256;
|
||||||
|
relay?: string;
|
||||||
|
}
|
||||||
export default function useThreadFeed(link: NostrLink) {
|
export default function useThreadFeed(link: NostrLink) {
|
||||||
const [trackingEvents, setTrackingEvent] = useState<u256[]>([link.id]);
|
const linkTagged = {
|
||||||
|
id: link.id,
|
||||||
|
relay: link.relays?.[0],
|
||||||
|
};
|
||||||
|
const [trackingEvents, setTrackingEvent] = useState<Array<RelayTaggedEventId>>([linkTagged]);
|
||||||
const [trackingATags, setTrackingATags] = useState<string[]>([]);
|
const [trackingATags, setTrackingATags] = useState<string[]>([]);
|
||||||
const [allEvents, setAllEvents] = useState<u256[]>([link.id]);
|
const [allEvents, setAllEvents] = useState<Array<RelayTaggedEventId>>([linkTagged]);
|
||||||
const pref = useLogin().preferences;
|
const pref = useLogin().preferences;
|
||||||
|
|
||||||
const sub = useMemo(() => {
|
const sub = useMemo(() => {
|
||||||
@ -17,7 +24,10 @@ export default function useThreadFeed(link: NostrLink) {
|
|||||||
sub.withOptions({
|
sub.withOptions({
|
||||||
leaveOpen: true,
|
leaveOpen: true,
|
||||||
});
|
});
|
||||||
sub.withFilter().ids(trackingEvents);
|
const fTracking = sub.withFilter();
|
||||||
|
for (const te of trackingEvents) {
|
||||||
|
fTracking.id(te.id, te.relay);
|
||||||
|
}
|
||||||
sub
|
sub
|
||||||
.withFilter()
|
.withFilter()
|
||||||
.kinds(
|
.kinds(
|
||||||
@ -25,7 +35,10 @@ export default function useThreadFeed(link: NostrLink) {
|
|||||||
? [EventKind.Reaction, EventKind.TextNote, EventKind.Repost, EventKind.ZapReceipt]
|
? [EventKind.Reaction, EventKind.TextNote, EventKind.Repost, EventKind.ZapReceipt]
|
||||||
: [EventKind.TextNote, EventKind.ZapReceipt, EventKind.Repost]
|
: [EventKind.TextNote, EventKind.ZapReceipt, EventKind.Repost]
|
||||||
)
|
)
|
||||||
.tag("e", allEvents);
|
.tag(
|
||||||
|
"e",
|
||||||
|
allEvents.map(a => a.id)
|
||||||
|
);
|
||||||
|
|
||||||
if (trackingATags.length > 0) {
|
if (trackingATags.length > 0) {
|
||||||
const parsed = trackingATags.map(a => a.split(":"));
|
const parsed = trackingATags.map(a => a.split(":"));
|
||||||
@ -45,16 +58,27 @@ export default function useThreadFeed(link: NostrLink) {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTrackingATags([]);
|
setTrackingATags([]);
|
||||||
setTrackingEvent([link.id]);
|
setTrackingEvent([linkTagged]);
|
||||||
setAllEvents([link.id]);
|
setAllEvents([linkTagged]);
|
||||||
}, [link.id]);
|
}, [link.id]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (store.data) {
|
if (store.data) {
|
||||||
const mainNotes = store.data?.filter(a => a.kind === EventKind.TextNote || a.kind === EventKind.Polls) ?? [];
|
const mainNotes = store.data?.filter(a => a.kind === EventKind.TextNote || a.kind === EventKind.Polls) ?? [];
|
||||||
|
|
||||||
const eTags = mainNotes.map(a => a.tags.filter(b => b[0] === "e").map(b => b[1])).flat();
|
const eTags = mainNotes
|
||||||
const eTagsMissing = eTags.filter(a => !mainNotes.some(b => b.id === a));
|
.map(a =>
|
||||||
|
a.tags
|
||||||
|
.filter(b => b[0] === "e")
|
||||||
|
.map(b => {
|
||||||
|
return {
|
||||||
|
id: b[1],
|
||||||
|
relay: b[2],
|
||||||
|
};
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.flat();
|
||||||
|
const eTagsMissing = eTags.filter(a => !mainNotes.some(b => b.id === a.id));
|
||||||
setTrackingEvent(s => appendDedupe(s, eTagsMissing));
|
setTrackingEvent(s => appendDedupe(s, eTagsMissing));
|
||||||
setAllEvents(s => appendDedupe(s, eTags));
|
setAllEvents(s => appendDedupe(s, eTags));
|
||||||
|
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
import { EventKind, u256 } from "@snort/nostr";
|
import { EventKind, u256, FlatNoteStore, RequestBuilder } from "@snort/system";
|
||||||
|
|
||||||
import { unixNow, unwrap, tagFilterOfTextRepost } from "Util";
|
import { unixNow, unwrap, tagFilterOfTextRepost } from "SnortUtils";
|
||||||
import { FlatNoteStore, RequestBuilder } from "System";
|
|
||||||
import useRequestBuilder from "Hooks/useRequestBuilder";
|
import useRequestBuilder from "Hooks/useRequestBuilder";
|
||||||
import useTimelineWindow from "Hooks/useTimelineWindow";
|
import useTimelineWindow from "Hooks/useTimelineWindow";
|
||||||
import useLogin from "Hooks/useLogin";
|
import useLogin from "Hooks/useLogin";
|
||||||
@ -134,7 +133,7 @@ export default function useTimelineFeed(subject: TimelineSubject, options: Timel
|
|||||||
}, [options.relay]);
|
}, [options.relay]);
|
||||||
|
|
||||||
const subNext = useMemo(() => {
|
const subNext = useMemo(() => {
|
||||||
const rb = new RequestBuilder(`timeline-related:${subject.type}`);
|
const rb = new RequestBuilder(`timeline-related:${subject.type}:${subject.discriminator}`);
|
||||||
if (trackingEvents.length > 0) {
|
if (trackingEvents.length > 0) {
|
||||||
rb.withFilter()
|
rb.withFilter()
|
||||||
.kinds(
|
.kinds(
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { HexKey, EventKind } from "@snort/nostr";
|
import { HexKey, EventKind, FlatNoteStore, RequestBuilder } from "@snort/system";
|
||||||
|
|
||||||
import { parseZap } from "Element/Zap";
|
import { parseZap } from "Element/Zap";
|
||||||
import { FlatNoteStore, RequestBuilder } from "System";
|
|
||||||
import useRequestBuilder from "Hooks/useRequestBuilder";
|
import useRequestBuilder from "Hooks/useRequestBuilder";
|
||||||
|
|
||||||
export default function useZapsFeed(pubkey?: HexKey) {
|
export default function useZapsFeed(pubkey?: HexKey) {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import * as utils from "@noble/curves/abstract/utils";
|
import * as utils from "@noble/curves/abstract/utils";
|
||||||
import * as base64 from "@protobufjs/base64";
|
import * as base64 from "@protobufjs/base64";
|
||||||
import { hmacSha256, unwrap } from "Util";
|
import { hmacSha256, unwrap } from "SnortUtils";
|
||||||
import useLogin from "Hooks/useLogin";
|
import useLogin from "Hooks/useLogin";
|
||||||
|
|
||||||
export interface ImgProxySettings {
|
export interface ImgProxySettings {
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { useSyncExternalStore } from "react";
|
import { useSyncExternalStore } from "react";
|
||||||
import { HexKey, u256 } from "@snort/nostr";
|
import { HexKey, u256 } from "@snort/system";
|
||||||
|
|
||||||
import { InteractionCache } from "Cache/EventInteractionCache";
|
import { InteractionCache } from "Cache/EventInteractionCache";
|
||||||
import { EventInteraction } from "Db";
|
import { EventInteraction } from "Db";
|
||||||
import { sha256, unwrap } from "Util";
|
import { sha256, unwrap } from "SnortUtils";
|
||||||
|
|
||||||
export function useInteractionCache(pubkey?: HexKey, event?: u256) {
|
export function useInteractionCache(pubkey?: HexKey, event?: u256) {
|
||||||
const id = event && pubkey ? sha256(event + pubkey) : undefined;
|
const id = event && pubkey ? sha256(event + pubkey) : undefined;
|
||||||
|
@ -4,7 +4,7 @@ import { EmailRegex, MnemonicRegex } from "Const";
|
|||||||
import { LoginStore } from "Login";
|
import { LoginStore } from "Login";
|
||||||
import { generateBip39Entropy, entropyToPrivateKey } from "nip6";
|
import { generateBip39Entropy, entropyToPrivateKey } from "nip6";
|
||||||
import { getNip05PubKey } from "Pages/LoginPage";
|
import { getNip05PubKey } from "Pages/LoginPage";
|
||||||
import { bech32ToHex } from "Util";
|
import { bech32ToHex } from "SnortUtils";
|
||||||
|
|
||||||
export default function useLoginHandler() {
|
export default function useLoginHandler() {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { HexKey } from "@snort/nostr";
|
import { HexKey } from "@snort/system";
|
||||||
import useEventPublisher from "Feed/EventPublisher";
|
import useEventPublisher from "Feed/EventPublisher";
|
||||||
import useLogin from "Hooks/useLogin";
|
import useLogin from "Hooks/useLogin";
|
||||||
import { setBlocked, setMuted } from "Login";
|
import { setBlocked, setMuted } from "Login";
|
||||||
import { appendDedupe } from "Util";
|
import { appendDedupe } from "SnortUtils";
|
||||||
|
|
||||||
export default function useModeration() {
|
export default function useModeration() {
|
||||||
const login = useLogin();
|
const login = useLogin();
|
||||||
|
@ -1,7 +1,13 @@
|
|||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { HexKey, Lists, EventKind } from "@snort/nostr";
|
import {
|
||||||
|
HexKey,
|
||||||
|
Lists,
|
||||||
|
EventKind,
|
||||||
|
FlatNoteStore,
|
||||||
|
ParameterizedReplaceableNoteStore,
|
||||||
|
RequestBuilder,
|
||||||
|
} from "@snort/system";
|
||||||
|
|
||||||
import { FlatNoteStore, ParameterizedReplaceableNoteStore, RequestBuilder } from "System";
|
|
||||||
import useRequestBuilder from "Hooks/useRequestBuilder";
|
import useRequestBuilder from "Hooks/useRequestBuilder";
|
||||||
import useLogin from "Hooks/useLogin";
|
import useLogin from "Hooks/useLogin";
|
||||||
|
|
||||||
|
@ -1,65 +0,0 @@
|
|||||||
import { HexKey } from "@snort/nostr";
|
|
||||||
import { useMemo } from "react";
|
|
||||||
import { FollowsRelays } from "State/Relays";
|
|
||||||
import { unwrap } from "Util";
|
|
||||||
|
|
||||||
export type RelayPicker = ReturnType<typeof useRelaysForFollows>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Number of relays to pick per pubkey
|
|
||||||
*/
|
|
||||||
const PickNRelays = 2;
|
|
||||||
|
|
||||||
export default function useRelaysForFollows(keys: Array<HexKey>) {
|
|
||||||
return useMemo(() => {
|
|
||||||
if (keys.length === 0) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
const allRelays = keys.map(a => {
|
|
||||||
return {
|
|
||||||
key: a,
|
|
||||||
relays: FollowsRelays.snapshot.get(a),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const missing = allRelays.filter(a => a.relays === undefined);
|
|
||||||
const hasRelays = allRelays.filter(a => a.relays !== undefined);
|
|
||||||
const relayUserMap = hasRelays.reduce((acc, v) => {
|
|
||||||
for (const r of unwrap(v.relays)) {
|
|
||||||
if (!acc.has(r.url)) {
|
|
||||||
acc.set(r.url, new Set([v.key]));
|
|
||||||
} else {
|
|
||||||
unwrap(acc.get(r.url)).add(v.key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
}, new Map<string, Set<HexKey>>());
|
|
||||||
const topRelays = [...relayUserMap.entries()].sort(([, v], [, v1]) => v1.size - v.size);
|
|
||||||
|
|
||||||
// <relay, key[]> - count keys per relay
|
|
||||||
// <key, relay[]> - pick n top relays
|
|
||||||
// <relay, key[]> - map keys per relay (for subscription filter)
|
|
||||||
|
|
||||||
const userPickedRelays = keys.map(k => {
|
|
||||||
// pick top 3 relays for this key
|
|
||||||
const relaysForKey = topRelays
|
|
||||||
.filter(([, v]) => v.has(k))
|
|
||||||
.slice(0, PickNRelays)
|
|
||||||
.map(([k]) => k);
|
|
||||||
return { k, relaysForKey };
|
|
||||||
});
|
|
||||||
|
|
||||||
const pickedRelays = new Set(userPickedRelays.map(a => a.relaysForKey).flat());
|
|
||||||
|
|
||||||
const picked = Object.fromEntries(
|
|
||||||
[...pickedRelays].map(a => {
|
|
||||||
const keysOnPickedRelay = new Set(userPickedRelays.filter(b => b.relaysForKey.includes(a)).map(b => b.k));
|
|
||||||
return [a, [...keysOnPickedRelay]];
|
|
||||||
})
|
|
||||||
);
|
|
||||||
picked[""] = missing.map(a => a.key);
|
|
||||||
console.debug(picked);
|
|
||||||
return picked;
|
|
||||||
}, [keys]);
|
|
||||||
}
|
|
@ -1,39 +1,30 @@
|
|||||||
import { useSyncExternalStore } from "react";
|
import { useSyncExternalStore } from "react";
|
||||||
import { RequestBuilder, System } from "System";
|
import { RequestBuilder, EmptySnapshot, NoteStore, StoreSnapshot } from "@snort/system";
|
||||||
import { EmptySnapshot, NoteStore, StoreSnapshot } from "System/NoteCollection";
|
import { unwrap } from "SnortUtils";
|
||||||
import { unwrap } from "Util";
|
import { System } from "index";
|
||||||
|
|
||||||
const useRequestBuilder = <TStore extends NoteStore, TSnapshot = ReturnType<TStore["getSnapshotData"]>>(
|
const useRequestBuilder = <TStore extends NoteStore, TSnapshot = ReturnType<TStore["getSnapshotData"]>>(
|
||||||
type: { new (): TStore },
|
type: { new (): TStore },
|
||||||
rb: RequestBuilder | null,
|
rb: RequestBuilder | null
|
||||||
debounced?: number
|
|
||||||
) => {
|
) => {
|
||||||
const subscribe = (onChanged: () => void) => {
|
const subscribe = (onChanged: () => void) => {
|
||||||
const store = System.Query<TStore>(type, rb);
|
if (rb) {
|
||||||
let t: ReturnType<typeof setTimeout> | undefined;
|
const q = System.Query<TStore>(type, rb);
|
||||||
const release = store.hook(() => {
|
const release = q.feed.hook(onChanged);
|
||||||
if (!t) {
|
q.uncancel();
|
||||||
t = setTimeout(() => {
|
return () => {
|
||||||
clearTimeout(t);
|
q.cancel();
|
||||||
t = undefined;
|
release();
|
||||||
onChanged();
|
};
|
||||||
}, debounced ?? 500);
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
if (rb?.id) {
|
// noop
|
||||||
System.CancelQuery(rb.id);
|
|
||||||
}
|
|
||||||
release();
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
const getState = (): StoreSnapshot<TSnapshot> => {
|
const getState = (): StoreSnapshot<TSnapshot> => {
|
||||||
if (rb?.id) {
|
const q = System.GetQuery(rb?.id ?? "");
|
||||||
const q = System.GetQuery(rb.id);
|
if (q) {
|
||||||
if (q) {
|
return unwrap(q).feed?.snapshot as StoreSnapshot<TSnapshot>;
|
||||||
return unwrap(q).feed?.snapshot as StoreSnapshot<TSnapshot>;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return EmptySnapshot as StoreSnapshot<TSnapshot>;
|
return EmptySnapshot as StoreSnapshot<TSnapshot>;
|
||||||
};
|
};
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import { useSyncExternalStore } from "react";
|
import { useSyncExternalStore } from "react";
|
||||||
import { System, SystemSnapshot } from "System";
|
import { SystemSnapshot } from "@snort/system";
|
||||||
|
import { System } from "index";
|
||||||
|
|
||||||
export default function useSystemState() {
|
export default function useSystemState() {
|
||||||
return useSyncExternalStore<SystemSnapshot>(
|
return useSyncExternalStore<SystemSnapshot>(
|
||||||
cb => System.hook(cb),
|
cb => System.hook(cb),
|
||||||
() => System.getSnapshot()
|
() => System.snapshot()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
import { useEffect, useSyncExternalStore } from "react";
|
import { useEffect, useSyncExternalStore } from "react";
|
||||||
import { HexKey } from "@snort/nostr";
|
|
||||||
|
|
||||||
import { MetadataCache } from "Cache";
|
import { HexKey, MetadataCache } from "@snort/system";
|
||||||
import { UserCache } from "Cache/UserCache";
|
import { UserCache } from "Cache/UserCache";
|
||||||
import { ProfileLoader } from "System/ProfileCache";
|
import { ProfileLoader } from "index";
|
||||||
|
|
||||||
export function useUserProfile(pubKey?: HexKey): MetadataCache | undefined {
|
export function useUserProfile(pubKey?: HexKey): MetadataCache | undefined {
|
||||||
const user = useSyncExternalStore<MetadataCache | undefined>(
|
const user = useSyncExternalStore<MetadataCache | undefined>(
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { HexKey, RawEvent } from "@snort/nostr";
|
import { HexKey, NostrEvent } from "@snort/system";
|
||||||
import { EmailRegex } from "Const";
|
import { EmailRegex } from "Const";
|
||||||
import { bech32ToText, unwrap } from "Util";
|
import { bech32ToText, unwrap } from "SnortUtils";
|
||||||
|
|
||||||
const PayServiceTag = "payRequest";
|
const PayServiceTag = "payRequest";
|
||||||
|
|
||||||
@ -119,7 +119,7 @@ export class LNURL {
|
|||||||
* @param zap
|
* @param zap
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
async getInvoice(amount: number, comment?: string, zap?: RawEvent) {
|
async getInvoice(amount: number, comment?: string, zap?: NostrEvent) {
|
||||||
const callback = new URL(unwrap(this.#service?.callback));
|
const callback = new URL(unwrap(this.#service?.callback));
|
||||||
const query = new Map<string, string>();
|
const query = new Map<string, string>();
|
||||||
|
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import { HexKey, RelaySettings } from "@snort/nostr";
|
import { HexKey, RelaySettings, EventPublisher } from "@snort/system";
|
||||||
import * as secp from "@noble/curves/secp256k1";
|
import * as secp from "@noble/curves/secp256k1";
|
||||||
import * as utils from "@noble/curves/abstract/utils";
|
import * as utils from "@noble/curves/abstract/utils";
|
||||||
|
|
||||||
import { DefaultRelays, SnortPubKey } from "Const";
|
import { DefaultRelays, SnortPubKey } from "Const";
|
||||||
import { LoginStore, UserPreferences, LoginSession } from "Login";
|
import { LoginStore, UserPreferences, LoginSession } from "Login";
|
||||||
import { generateBip39Entropy, entropyToPrivateKey } from "nip6";
|
import { generateBip39Entropy, entropyToPrivateKey } from "nip6";
|
||||||
import { bech32ToHex, dedupeById, randomSample, sanitizeRelayUrl, unixNowMs, unwrap } from "Util";
|
import { bech32ToHex, dedupeById, randomSample, sanitizeRelayUrl, unixNowMs, unwrap } from "SnortUtils";
|
||||||
import { SubscriptionEvent } from "Subscription";
|
import { SubscriptionEvent } from "Subscription";
|
||||||
import { EventPublisher } from "System/EventPublisher";
|
import { System } from "index";
|
||||||
|
|
||||||
export function setRelays(state: LoginSession, relays: Record<string, RelaySettings>, createdAt: number) {
|
export function setRelays(state: LoginSession, relays: Record<string, RelaySettings>, createdAt: number) {
|
||||||
if (state.relays.timestamp >= createdAt) {
|
if (state.relays.timestamp >= createdAt) {
|
||||||
@ -78,7 +78,7 @@ export async function generateNewLogin() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const publicKey = utils.bytesToHex(secp.schnorr.getPublicKey(privateKey));
|
const publicKey = utils.bytesToHex(secp.schnorr.getPublicKey(privateKey));
|
||||||
const publisher = new EventPublisher(publicKey, privateKey);
|
const publisher = new EventPublisher(System, publicKey, privateKey);
|
||||||
const ev = await publisher.contactList([bech32ToHex(SnortPubKey), publicKey], newRelays);
|
const ev = await publisher.contactList([bech32ToHex(SnortPubKey), publicKey], newRelays);
|
||||||
publisher.broadcast(ev);
|
publisher.broadcast(ev);
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { HexKey, RelaySettings, u256 } from "@snort/nostr";
|
import { HexKey, RelaySettings, u256 } from "@snort/system";
|
||||||
import { UserPreferences } from "Login";
|
import { UserPreferences } from "Login";
|
||||||
import { SubscriptionEvent } from "Subscription";
|
import { SubscriptionEvent } from "Subscription";
|
||||||
|
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import * as secp from "@noble/curves/secp256k1";
|
import * as secp from "@noble/curves/secp256k1";
|
||||||
import * as utils from "@noble/curves/abstract/utils";
|
import * as utils from "@noble/curves/abstract/utils";
|
||||||
|
|
||||||
import { HexKey, RelaySettings } from "@snort/nostr";
|
import { HexKey, RelaySettings } from "@snort/system";
|
||||||
|
|
||||||
import { DefaultRelays } from "Const";
|
import { DefaultRelays } from "Const";
|
||||||
import ExternalStore from "ExternalStore";
|
import ExternalStore from "ExternalStore";
|
||||||
import { LoginSession } from "Login";
|
import { LoginSession } from "Login";
|
||||||
import { deepClone, sanitizeRelayUrl, unwrap } from "Util";
|
import { deepClone, sanitizeRelayUrl, unwrap } from "SnortUtils";
|
||||||
import { DefaultPreferences, UserPreferences } from "./Preferences";
|
import { DefaultPreferences, UserPreferences } from "./Preferences";
|
||||||
|
|
||||||
const AccountStoreKey = "sessions";
|
const AccountStoreKey = "sessions";
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { EventKind } from "@snort/nostr";
|
import { EventKind, EventPublisher } from "@snort/system";
|
||||||
import { EventPublisher } from "System/EventPublisher";
|
|
||||||
import { ServiceError, ServiceProvider } from "./ServiceProvider";
|
import { ServiceError, ServiceProvider } from "./ServiceProvider";
|
||||||
|
|
||||||
export interface ManageHandle {
|
export interface ManageHandle {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import DnsOverHttpResolver from "dns-over-http-resolver";
|
import DnsOverHttpResolver from "dns-over-http-resolver";
|
||||||
import { bech32ToHex } from "Util";
|
import { bech32ToHex } from "SnortUtils";
|
||||||
|
|
||||||
const resolver = new DnsOverHttpResolver();
|
const resolver = new DnsOverHttpResolver();
|
||||||
interface NostrJson {
|
interface NostrJson {
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
import Nostrich from "nostrich.webp";
|
import Nostrich from "nostrich.webp";
|
||||||
|
|
||||||
import { TaggedRawEvent } from "@snort/nostr";
|
import { TaggedRawEvent, EventKind, MetadataCache } from "@snort/system";
|
||||||
import { EventKind } from "@snort/nostr";
|
|
||||||
import { MetadataCache } from "Cache";
|
|
||||||
import { getDisplayName } from "Element/ProfileImage";
|
import { getDisplayName } from "Element/ProfileImage";
|
||||||
import { MentionRegex } from "Const";
|
import { MentionRegex } from "Const";
|
||||||
import { tagFilterOfTextRepost, unwrap } from "Util";
|
import { tagFilterOfTextRepost, unwrap } from "SnortUtils";
|
||||||
import { UserCache } from "Cache/UserCache";
|
import { UserCache } from "Cache/UserCache";
|
||||||
import { LoginSession } from "Login";
|
import { LoginSession } from "Login";
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import DmWindow from "Element/DmWindow";
|
import DmWindow from "Element/DmWindow";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import { bech32ToHex } from "Util";
|
import { bech32ToHex } from "SnortUtils";
|
||||||
|
|
||||||
import "./ChatPage.css";
|
import "./ChatPage.css";
|
||||||
|
|
||||||
|
9
packages/app/src/Pages/Debug.tsx
Normal file
9
packages/app/src/Pages/Debug.tsx
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import SubDebug from "Element/SubDebug";
|
||||||
|
|
||||||
|
export default function DebugPage() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SubDebug />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
@ -1,11 +1,11 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
import { HexKey } from "@snort/nostr";
|
import { HexKey } from "@snort/system";
|
||||||
|
|
||||||
import { ApiHost, KieranPubKey, SnortPubKey } from "Const";
|
import { ApiHost, KieranPubKey, SnortPubKey } from "Const";
|
||||||
import ProfilePreview from "Element/ProfilePreview";
|
import ProfilePreview from "Element/ProfilePreview";
|
||||||
import ZapButton from "Element/ZapButton";
|
import ZapButton from "Element/ZapButton";
|
||||||
import { bech32ToHex } from "Util";
|
import { bech32ToHex } from "SnortUtils";
|
||||||
import SnortApi, { RevenueSplit, RevenueToday } from "SnortApi";
|
import SnortApi, { RevenueSplit, RevenueToday } from "SnortApi";
|
||||||
|
|
||||||
const Developers = [
|
const Developers = [
|
||||||
|
@ -9,21 +9,18 @@ import messages from "./messages";
|
|||||||
import Icon from "Icons/Icon";
|
import Icon from "Icons/Icon";
|
||||||
import { RootState } from "State/Store";
|
import { RootState } from "State/Store";
|
||||||
import { setShow, reset } from "State/NoteCreator";
|
import { setShow, reset } from "State/NoteCreator";
|
||||||
import { System } from "System";
|
import { System } from "index";
|
||||||
import useLoginFeed from "Feed/LoginFeed";
|
import useLoginFeed from "Feed/LoginFeed";
|
||||||
import { totalUnread } from "Pages/MessagesPage";
|
import { totalUnread } from "Pages/MessagesPage";
|
||||||
import useModeration from "Hooks/useModeration";
|
import useModeration from "Hooks/useModeration";
|
||||||
import { NoteCreator } from "Element/NoteCreator";
|
import { NoteCreator } from "Element/NoteCreator";
|
||||||
import { db } from "Db";
|
|
||||||
import useEventPublisher from "Feed/EventPublisher";
|
import useEventPublisher from "Feed/EventPublisher";
|
||||||
import SubDebug from "Element/SubDebug";
|
|
||||||
import { preload } from "Cache";
|
|
||||||
import { useDmCache } from "Hooks/useDmsCache";
|
import { useDmCache } from "Hooks/useDmsCache";
|
||||||
import { mapPlanName } from "./subscribe";
|
import { mapPlanName } from "./subscribe";
|
||||||
import useLogin from "Hooks/useLogin";
|
import useLogin from "Hooks/useLogin";
|
||||||
import Avatar from "Element/Avatar";
|
import Avatar from "Element/Avatar";
|
||||||
import { useUserProfile } from "Hooks/useUserProfile";
|
import { useUserProfile } from "Hooks/useUserProfile";
|
||||||
import { profileLink } from "Util";
|
import { profileLink } from "SnortUtils";
|
||||||
import { getCurrentSubscription } from "Subscription";
|
import { getCurrentSubscription } from "Subscription";
|
||||||
import Toaster from "Toaster";
|
import Toaster from "Toaster";
|
||||||
|
|
||||||
@ -78,9 +75,9 @@ export default function Layout() {
|
|||||||
for (const [k, v] of Object.entries(relays.item)) {
|
for (const [k, v] of Object.entries(relays.item)) {
|
||||||
await System.ConnectToRelay(k, v);
|
await System.ConnectToRelay(k, v);
|
||||||
}
|
}
|
||||||
for (const [k, c] of System.Sockets) {
|
for (const v of System.Sockets) {
|
||||||
if (!relays.item[k] && !c.Ephemeral) {
|
if (!relays.item[v.address] && !v.ephemeral) {
|
||||||
System.DisconnectRelay(k);
|
System.DisconnectRelay(v.address);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
@ -112,29 +109,6 @@ export default function Layout() {
|
|||||||
};
|
};
|
||||||
}, [preferences.theme]);
|
}, [preferences.theme]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// check DB support then init
|
|
||||||
db.isAvailable().then(async a => {
|
|
||||||
db.ready = a;
|
|
||||||
if (a) {
|
|
||||||
await preload();
|
|
||||||
}
|
|
||||||
console.debug(`Using db: ${a ? "IndexedDB" : "In-Memory"}`);
|
|
||||||
|
|
||||||
try {
|
|
||||||
if ("registerProtocolHandler" in window.navigator) {
|
|
||||||
window.navigator.registerProtocolHandler(
|
|
||||||
"web+nostr",
|
|
||||||
`${window.location.protocol}//${window.location.host}/%s`
|
|
||||||
);
|
|
||||||
console.info("Registered protocol handler for 'web+nostr'");
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error("Failed to register protocol handler", e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={pageClass}>
|
<div className={pageClass}>
|
||||||
{!shouldHideHeader && (
|
{!shouldHideHeader && (
|
||||||
@ -170,7 +144,6 @@ export default function Layout() {
|
|||||||
<NoteCreator />
|
<NoteCreator />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{window.localStorage.getItem("debug") && <SubDebug />}
|
|
||||||
<Toaster />
|
<Toaster />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -3,9 +3,9 @@ import "./LoginPage.css";
|
|||||||
import { CSSProperties, useEffect, useState } from "react";
|
import { CSSProperties, useEffect, useState } from "react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { useIntl, FormattedMessage } from "react-intl";
|
import { useIntl, FormattedMessage } from "react-intl";
|
||||||
import { HexKey } from "@snort/nostr";
|
import { HexKey } from "@snort/system";
|
||||||
|
|
||||||
import { bech32ToHex, unwrap } from "Util";
|
import { bech32ToHex, unwrap } from "SnortUtils";
|
||||||
import ZapButton from "Element/ZapButton";
|
import ZapButton from "Element/ZapButton";
|
||||||
import useImgProxy from "Hooks/useImgProxy";
|
import useImgProxy from "Hooks/useImgProxy";
|
||||||
import Icon from "Icons/Icon";
|
import Icon from "Icons/Icon";
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import React, { useMemo, useState } from "react";
|
import React, { useMemo, useState } from "react";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { HexKey, RawEvent, NostrPrefix } from "@snort/nostr";
|
import { HexKey, NostrEvent, NostrPrefix } from "@snort/system";
|
||||||
|
|
||||||
import UnreadCount from "Element/UnreadCount";
|
import UnreadCount from "Element/UnreadCount";
|
||||||
import ProfileImage, { getDisplayName } from "Element/ProfileImage";
|
import ProfileImage, { getDisplayName } from "Element/ProfileImage";
|
||||||
import { dedupe, hexToBech32, unwrap } from "Util";
|
import { dedupe, hexToBech32, unwrap } from "SnortUtils";
|
||||||
import NoteToSelf from "Element/NoteToSelf";
|
import NoteToSelf from "Element/NoteToSelf";
|
||||||
import useModeration from "Hooks/useModeration";
|
import useModeration from "Hooks/useModeration";
|
||||||
import { useDmCache } from "Hooks/useDmsCache";
|
import { useDmCache } from "Hooks/useDmsCache";
|
||||||
@ -162,30 +162,30 @@ export function setLastReadDm(pk: HexKey) {
|
|||||||
window.localStorage.setItem(k, now.toString());
|
window.localStorage.setItem(k, now.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
export function dmTo(e: RawEvent) {
|
export function dmTo(e: NostrEvent) {
|
||||||
const firstP = e.tags.find(b => b[0] === "p");
|
const firstP = e.tags.find(b => b[0] === "p");
|
||||||
return unwrap(firstP?.[1]);
|
return unwrap(firstP?.[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isToSelf(e: Readonly<RawEvent>, pk: HexKey) {
|
export function isToSelf(e: Readonly<NostrEvent>, pk: HexKey) {
|
||||||
return e.pubkey === pk && dmTo(e) === pk;
|
return e.pubkey === pk && dmTo(e) === pk;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function dmsInChat(dms: readonly RawEvent[], pk: HexKey) {
|
export function dmsInChat(dms: readonly NostrEvent[], pk: HexKey) {
|
||||||
return dms.filter(a => a.pubkey === pk || dmTo(a) === pk);
|
return dms.filter(a => a.pubkey === pk || dmTo(a) === pk);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function totalUnread(dms: RawEvent[], myPubKey: HexKey) {
|
export function totalUnread(dms: NostrEvent[], myPubKey: HexKey) {
|
||||||
return extractChats(dms, myPubKey).reduce((acc, v) => (acc += v.unreadMessages), 0);
|
return extractChats(dms, myPubKey).reduce((acc, v) => (acc += v.unreadMessages), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
function unreadDms(dms: RawEvent[], myPubKey: HexKey, pk: HexKey) {
|
function unreadDms(dms: NostrEvent[], myPubKey: HexKey, pk: HexKey) {
|
||||||
if (pk === myPubKey) return 0;
|
if (pk === myPubKey) return 0;
|
||||||
const lastRead = lastReadDm(pk);
|
const lastRead = lastReadDm(pk);
|
||||||
return dmsInChat(dms, pk).filter(a => a.created_at >= lastRead && a.pubkey !== myPubKey).length;
|
return dmsInChat(dms, pk).filter(a => a.created_at >= lastRead && a.pubkey !== myPubKey).length;
|
||||||
}
|
}
|
||||||
|
|
||||||
function newestMessage(dms: readonly RawEvent[], myPubKey: HexKey, pk: HexKey) {
|
function newestMessage(dms: readonly NostrEvent[], myPubKey: HexKey, pk: HexKey) {
|
||||||
if (pk === myPubKey) {
|
if (pk === myPubKey) {
|
||||||
return dmsInChat(
|
return dmsInChat(
|
||||||
dms.filter(d => isToSelf(d, myPubKey)),
|
dms.filter(d => isToSelf(d, myPubKey)),
|
||||||
@ -196,11 +196,11 @@ function newestMessage(dms: readonly RawEvent[], myPubKey: HexKey, pk: HexKey) {
|
|||||||
return dmsInChat(dms, pk).reduce((acc, v) => (acc = v.created_at > acc ? v.created_at : acc), 0);
|
return dmsInChat(dms, pk).reduce((acc, v) => (acc = v.created_at > acc ? v.created_at : acc), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function dmsForLogin(dms: readonly RawEvent[], myPubKey: HexKey) {
|
export function dmsForLogin(dms: readonly NostrEvent[], myPubKey: HexKey) {
|
||||||
return dms.filter(a => a.pubkey === myPubKey || (a.pubkey !== myPubKey && dmTo(a) === myPubKey));
|
return dms.filter(a => a.pubkey === myPubKey || (a.pubkey !== myPubKey && dmTo(a) === myPubKey));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function extractChats(dms: RawEvent[], myPubKey: HexKey) {
|
export function extractChats(dms: NostrEvent[], myPubKey: HexKey) {
|
||||||
const myDms = dmsForLogin(dms, myPubKey);
|
const myDms = dmsForLogin(dms, myPubKey);
|
||||||
const keys = myDms.map(a => [a.pubkey, dmTo(a)]).flat();
|
const keys = myDms.map(a => [a.pubkey, dmTo(a)]).flat();
|
||||||
const filteredKeys = dedupe(keys);
|
const filteredKeys = dedupe(keys);
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
import { NostrPrefix } from "@snort/nostr";
|
import { NostrPrefix, parseNostrLink } from "@snort/system";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
import { useNavigate, useParams } from "react-router-dom";
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
|
|
||||||
import Spinner from "Icons/Spinner";
|
import Spinner from "Icons/Spinner";
|
||||||
import { parseNostrLink, profileLink } from "Util";
|
import { profileLink } from "SnortUtils";
|
||||||
import { getNip05PubKey } from "Pages/LoginPage";
|
import { getNip05PubKey } from "Pages/LoginPage";
|
||||||
import { System } from "System";
|
|
||||||
|
|
||||||
export default function NostrLinkHandler() {
|
export default function NostrLinkHandler() {
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
@ -18,9 +17,6 @@ export default function NostrLinkHandler() {
|
|||||||
async function handleLink(link: string) {
|
async function handleLink(link: string) {
|
||||||
const nav = parseNostrLink(link);
|
const nav = parseNostrLink(link);
|
||||||
if (nav) {
|
if (nav) {
|
||||||
if ((nav.relays?.length ?? 0) > 0) {
|
|
||||||
nav.relays?.map(a => System.ConnectEphemeralRelay(a));
|
|
||||||
}
|
|
||||||
if (nav.type === NostrPrefix.Event || nav.type === NostrPrefix.Note || nav.type === NostrPrefix.Address) {
|
if (nav.type === NostrPrefix.Event || nav.type === NostrPrefix.Note || nav.type === NostrPrefix.Address) {
|
||||||
navigate(`/e/${nav.encode()}`);
|
navigate(`/e/${nav.encode()}`);
|
||||||
} else if (nav.type === NostrPrefix.PublicKey || nav.type === NostrPrefix.Profile) {
|
} else if (nav.type === NostrPrefix.PublicKey || nav.type === NostrPrefix.Profile) {
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user