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
|
||||
.yarn
|
||||
yarn.lock
|
||||
dist/
|
||||
*.tgz
|
||||
*.log
|
@ -4,8 +4,9 @@
|
||||
"packages/*"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "yarn workspace @snort/nostr build && yarn workspace @snort/app build",
|
||||
"start": "yarn workspace @snort/nostr build && yarn workspace @snort/app start"
|
||||
"build": "yarn workspace @snort/system build && yarn workspace @snort/app build",
|
||||
"start": "yarn workspace @snort/system build && yarn workspace @snort/app start",
|
||||
"test": "yarn workspace @snort/app test"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@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*
|
||||
|
||||
.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",
|
||||
"@scure/bip32": "^1.3.0",
|
||||
"@scure/bip39": "^1.1.1",
|
||||
"@snort/nostr": "^1.0.0",
|
||||
"@szhsin/react-menu": "^3.3.1",
|
||||
"@void-cat/api": "^1.0.4",
|
||||
"base32-decode": "^1.0.0",
|
||||
"bech32": "^2.0.0",
|
||||
"debug": "^4.3.4",
|
||||
"dexie": "^3.2.2",
|
||||
"dexie-react-hooks": "^1.1.1",
|
||||
"dns-over-http-resolver": "^2.1.1",
|
||||
@ -48,7 +48,7 @@
|
||||
"scripts": {
|
||||
"start": "webpack serve",
|
||||
"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-compile": "formatjs compile src/lang.json --out-file src/translations/en.json",
|
||||
"format": "prettier --write .",
|
||||
@ -80,7 +80,8 @@
|
||||
"@babel/preset-react": "^7.18.6",
|
||||
"@formatjs/cli": "^6.0.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/react": "^18.0.26",
|
||||
"@types/react-dom": "^18.0.10",
|
||||
@ -99,9 +100,12 @@
|
||||
"eslint-webpack-plugin": "^4.0.1",
|
||||
"html-webpack-plugin": "^5.5.1",
|
||||
"husky": ">=6",
|
||||
"jest": "^29.5.0",
|
||||
"jest-environment-jsdom": "^29.5.0",
|
||||
"lint-staged": ">=10",
|
||||
"mini-css-extract-plugin": "^2.7.5",
|
||||
"prettier": "2.8.3",
|
||||
"ts-jest": "^29.1.0",
|
||||
"ts-loader": "^9.4.2",
|
||||
"typescript": "^5.0.4",
|
||||
"webpack": "^5.82.1",
|
||||
|
@ -1,13 +1,13 @@
|
||||
import { RawEvent } from "@snort/nostr";
|
||||
import { NostrEvent } from "@snort/system";
|
||||
import { db } from "Db";
|
||||
import FeedCache from "./FeedCache";
|
||||
|
||||
class DMCache extends FeedCache<RawEvent> {
|
||||
class DMCache extends FeedCache<NostrEvent> {
|
||||
constructor() {
|
||||
super("DMCache", db.dms);
|
||||
}
|
||||
|
||||
key(of: RawEvent): string {
|
||||
key(of: NostrEvent): string {
|
||||
return of.id;
|
||||
}
|
||||
|
||||
@ -23,11 +23,11 @@ class DMCache extends FeedCache<RawEvent> {
|
||||
return ret;
|
||||
}
|
||||
|
||||
allDms(): Array<RawEvent> {
|
||||
allDms(): Array<NostrEvent> {
|
||||
return [...this.cache.values()];
|
||||
}
|
||||
|
||||
takeSnapshot(): Array<RawEvent> {
|
||||
takeSnapshot(): Array<NostrEvent> {
|
||||
return this.allDms();
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { db, EventInteraction } from "Db";
|
||||
import { LoginStore } from "Login";
|
||||
import { sha256 } from "Util";
|
||||
import { sha256 } from "SnortUtils";
|
||||
import FeedCache from "./FeedCache";
|
||||
|
||||
class EventInteractionCache extends FeedCache<EventInteraction> {
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { db } from "Db";
|
||||
import debug from "debug";
|
||||
import { Table } from "dexie";
|
||||
import { unixNowMs, unwrap } from "Util";
|
||||
import { unixNowMs, unwrap } from "SnortUtils";
|
||||
|
||||
type HookFn = () => void;
|
||||
|
||||
@ -11,31 +12,32 @@ interface HookFilter {
|
||||
|
||||
export default abstract class FeedCache<TCached> {
|
||||
#name: string;
|
||||
#table: Table<TCached>;
|
||||
#hooks: Array<HookFilter> = [];
|
||||
#snapshot: Readonly<Array<TCached>> = [];
|
||||
#changed = true;
|
||||
#hits = 0;
|
||||
#miss = 0;
|
||||
protected table: Table<TCached>;
|
||||
protected onTable: Set<string> = new Set();
|
||||
protected cache: Map<string, TCached> = new Map();
|
||||
|
||||
constructor(name: string, table: Table<TCached>) {
|
||||
this.#name = name;
|
||||
this.#table = table;
|
||||
this.table = table;
|
||||
setInterval(() => {
|
||||
console.debug(
|
||||
`[${this.#name}] ${this.cache.size} loaded, ${this.onTable.size} on-disk, ${this.#hooks.length} hooks, ${(
|
||||
(this.#hits / (this.#hits + this.#miss)) *
|
||||
100
|
||||
).toFixed(1)} % hit`
|
||||
debug(this.#name)(
|
||||
"%d loaded, %d on-disk, %d hooks, %d% hit",
|
||||
this.cache.size,
|
||||
this.onTable.size,
|
||||
this.#hooks.length,
|
||||
((this.#hits / (this.#hits + this.#miss)) * 100).toFixed(1)
|
||||
);
|
||||
}, 5_000);
|
||||
}, 30_000);
|
||||
}
|
||||
|
||||
async preload() {
|
||||
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));
|
||||
}
|
||||
}
|
||||
@ -73,7 +75,7 @@ export default abstract class FeedCache<TCached> {
|
||||
|
||||
async get(key?: string) {
|
||||
if (key && !this.cache.has(key) && db.ready) {
|
||||
const cached = await this.#table.get(key);
|
||||
const cached = await this.table.get(key);
|
||||
if (cached) {
|
||||
this.cache.set(this.key(cached), cached);
|
||||
this.notifyChange([key]);
|
||||
@ -86,7 +88,7 @@ export default abstract class FeedCache<TCached> {
|
||||
async bulkGet(keys: Array<string>) {
|
||||
const missing = keys.filter(a => !this.cache.has(a));
|
||||
if (missing.length > 0 && db.ready) {
|
||||
const cached = await this.#table.bulkGet(missing);
|
||||
const cached = await this.table.bulkGet(missing);
|
||||
cached.forEach(a => {
|
||||
if (a) {
|
||||
this.cache.set(this.key(a), a);
|
||||
@ -103,7 +105,7 @@ export default abstract class FeedCache<TCached> {
|
||||
const k = this.key(obj);
|
||||
this.cache.set(k, obj);
|
||||
if (db.ready) {
|
||||
await this.#table.put(obj);
|
||||
await this.table.put(obj);
|
||||
this.onTable.add(k);
|
||||
}
|
||||
this.notifyChange([k]);
|
||||
@ -111,13 +113,44 @@ export default abstract class FeedCache<TCached> {
|
||||
|
||||
async bulkSet(obj: Array<TCached>) {
|
||||
if (db.ready) {
|
||||
await this.#table.bulkPut(obj);
|
||||
await this.table.bulkPut(obj);
|
||||
obj.forEach(a => this.onTable.add(this.key(a)));
|
||||
}
|
||||
obj.forEach(v => this.cache.set(this.key(v), v));
|
||||
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
|
||||
* @param keys List of ids to load
|
||||
@ -131,16 +164,17 @@ export default abstract class FeedCache<TCached> {
|
||||
key: a,
|
||||
}));
|
||||
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));
|
||||
fromCacheFiltered.forEach(a => {
|
||||
this.cache.set(this.key(a), a);
|
||||
});
|
||||
this.notifyChange(fromCacheFiltered.map(a => this.key(a)));
|
||||
console.debug(
|
||||
`[${this.#name}] Loaded ${fromCacheFiltered.length}/${keys.length} in ${(
|
||||
unixNowMs() - start
|
||||
).toLocaleString()} ms`
|
||||
debug(this.#name)(
|
||||
`Loaded %d/%d in %d ms`,
|
||||
fromCacheFiltered.length,
|
||||
keys.length,
|
||||
(unixNowMs() - start).toLocaleString()
|
||||
);
|
||||
return mapped.filter(a => !a.has).map(a => a.key);
|
||||
}
|
||||
@ -150,7 +184,7 @@ export default abstract class FeedCache<TCached> {
|
||||
}
|
||||
|
||||
async clear() {
|
||||
await this.#table.clear();
|
||||
await this.table.clear();
|
||||
this.cache.clear();
|
||||
this.onTable.clear();
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import FeedCache from "Cache/FeedCache";
|
||||
import { db } from "Db";
|
||||
import { MetadataCache } from "Cache";
|
||||
import { MetadataCache } from "@snort/system";
|
||||
import { LNURL } from "LNURL";
|
||||
import { fetchNip05Pubkey } from "Nip05/Verifier";
|
||||
|
||||
@ -18,6 +18,14 @@ class UserProfileCache extends FeedCache<MetadataCache> {
|
||||
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>> {
|
||||
if (db.ready) {
|
||||
// on-disk cache will always have more data
|
||||
@ -53,28 +61,9 @@ class UserProfileCache extends FeedCache<MetadataCache> {
|
||||
* @param m Profile metadata
|
||||
* @returns
|
||||
*/
|
||||
async update(m: MetadataCache) {
|
||||
const existing = this.getFromCache(m.pubkey);
|
||||
const updateType = (() => {
|
||||
if (!existing) {
|
||||
return "new_profile";
|
||||
}
|
||||
if (existing.created < m.created) {
|
||||
return "updated_profile";
|
||||
}
|
||||
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") {
|
||||
override async update(m: MetadataCache) {
|
||||
const updateType = await super.update(m);
|
||||
if (updateType !== "refresh") {
|
||||
const lnurl = m.lud16 ?? m.lud06;
|
||||
if (lnurl) {
|
||||
this.#zapperQueue.push({
|
||||
@ -82,7 +71,6 @@ class UserProfileCache extends FeedCache<MetadataCache> {
|
||||
lnurl,
|
||||
});
|
||||
}
|
||||
}
|
||||
if (m.nip05) {
|
||||
this.#nip5Queue.push({
|
||||
pubkey: m.pubkey,
|
||||
@ -97,15 +85,6 @@ class UserProfileCache extends FeedCache<MetadataCache> {
|
||||
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() {
|
||||
await this.#batchQueue(
|
||||
this.#zapperQueue,
|
||||
@ -114,7 +93,7 @@ class UserProfileCache extends FeedCache<MetadataCache> {
|
||||
await svc.load();
|
||||
const p = this.getFromCache(i.pubkey);
|
||||
if (p) {
|
||||
this.#setItem({
|
||||
await this.set({
|
||||
...p,
|
||||
zapService: svc.zapperPubkey,
|
||||
});
|
||||
@ -134,7 +113,7 @@ class UserProfileCache extends FeedCache<MetadataCache> {
|
||||
const nip5pk = await fetchNip05Pubkey(name, domain);
|
||||
const p = this.getFromCache(i.pubkey);
|
||||
if (p) {
|
||||
this.#setItem({
|
||||
await this.set({
|
||||
...p,
|
||||
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 { InteractionCache } from "./EventInteractionCache";
|
||||
import { UserCache } from "./UserCache";
|
||||
import { UserRelays } from "./UserRelayCache";
|
||||
|
||||
export interface MetadataCache extends UserMetadata {
|
||||
/**
|
||||
* When the object was saved in cache
|
||||
*/
|
||||
loaded: number;
|
||||
|
||||
/**
|
||||
* When the source metadata event was created
|
||||
*/
|
||||
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 async function preload(follows?: Array<string>) {
|
||||
const preloads = [
|
||||
UserCache.preload(follows),
|
||||
DmCache.preload(),
|
||||
InteractionCache.preload(),
|
||||
UserRelays.preload(follows),
|
||||
];
|
||||
await Promise.all(preloads);
|
||||
}
|
||||
|
||||
export { UserCache, DmCache };
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { RelaySettings } from "@snort/nostr";
|
||||
import { RelaySettings } from "@snort/system";
|
||||
|
||||
/**
|
||||
* Add-on api for snort features
|
||||
@ -33,7 +33,7 @@ export const DefaultConnectTimeout = 2000;
|
||||
/**
|
||||
* 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
|
||||
|
@ -1,6 +1,5 @@
|
||||
import Dexie, { Table } from "dexie";
|
||||
import { FullRelaySettings, HexKey, RawEvent, u256 } from "@snort/nostr";
|
||||
import { MetadataCache } from "Cache";
|
||||
import { FullRelaySettings, HexKey, NostrEvent, u256, MetadataCache } from "@snort/system";
|
||||
|
||||
export const NAME = "snortDB";
|
||||
export const VERSION = 9;
|
||||
@ -21,6 +20,7 @@ export interface RelayMetrics {
|
||||
|
||||
export interface UsersRelays {
|
||||
pubkey: HexKey;
|
||||
created_at: number;
|
||||
relays: FullRelaySettings[];
|
||||
}
|
||||
|
||||
@ -55,8 +55,8 @@ export class SnortDB extends Dexie {
|
||||
users!: Table<MetadataCache>;
|
||||
relayMetrics!: Table<RelayMetrics>;
|
||||
userRelays!: Table<UsersRelays>;
|
||||
events!: Table<RawEvent>;
|
||||
dms!: Table<RawEvent>;
|
||||
events!: Table<NostrEvent>;
|
||||
dms!: Table<NostrEvent>;
|
||||
eventInteraction!: Table<EventInteraction>;
|
||||
payments!: Table<Payment>;
|
||||
|
||||
|
@ -2,7 +2,7 @@ import "./Avatar.css";
|
||||
import Nostrich from "nostrich.webp";
|
||||
|
||||
import { CSSProperties, useEffect, useState } from "react";
|
||||
import type { UserMetadata } from "@snort/nostr";
|
||||
import type { UserMetadata } from "@snort/system";
|
||||
|
||||
import useImgProxy from "Hooks/useImgProxy";
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import Icon from "Icons/Icon";
|
||||
import { useState } from "react";
|
||||
import useFileUpload from "Upload";
|
||||
import { openFile, unwrap } from "Util";
|
||||
import { openFile, unwrap } from "SnortUtils";
|
||||
|
||||
interface AvatarEditorProps {
|
||||
picture?: string;
|
||||
|
@ -3,13 +3,13 @@ import "./BadgeList.css";
|
||||
import { useState } from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
import { TaggedRawEvent } from "@snort/nostr";
|
||||
import { TaggedRawEvent } from "@snort/system";
|
||||
|
||||
import { ProxyImg } from "Element/ProxyImg";
|
||||
import Icon from "Icons/Icon";
|
||||
import Modal from "Element/Modal";
|
||||
import Username from "Element/Username";
|
||||
import { findTag } from "Util";
|
||||
import { findTag } from "SnortUtils";
|
||||
|
||||
export default function BadgeList({ badges }: { badges: TaggedRawEvent[] }) {
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import { HexKey } from "@snort/nostr";
|
||||
import { HexKey } from "@snort/system";
|
||||
import useModeration from "Hooks/useModeration";
|
||||
|
||||
import messages from "./messages";
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useState, useMemo, ChangeEvent } from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import { HexKey, TaggedRawEvent } from "@snort/nostr";
|
||||
import { HexKey, TaggedRawEvent } from "@snort/system";
|
||||
|
||||
import Note from "Element/Note";
|
||||
import useLogin from "Hooks/useLogin";
|
||||
|
@ -2,13 +2,13 @@ import "./DM.css";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
import { useInView } from "react-intersection-observer";
|
||||
import { TaggedRawEvent } from "@snort/nostr";
|
||||
import { TaggedRawEvent } from "@snort/system";
|
||||
|
||||
import useEventPublisher from "Feed/EventPublisher";
|
||||
import NoteTime from "Element/NoteTime";
|
||||
import Text from "Element/Text";
|
||||
import { setLastReadDm } from "Pages/MessagesPage";
|
||||
import { unwrap } from "Util";
|
||||
import { unwrap } from "SnortUtils";
|
||||
import useLogin from "Hooks/useLogin";
|
||||
|
||||
import messages from "./messages";
|
||||
|
@ -1,6 +1,6 @@
|
||||
import "./DmWindow.css";
|
||||
import { useEffect, useMemo, useRef } from "react";
|
||||
import { TaggedRawEvent } from "@snort/nostr";
|
||||
import { TaggedRawEvent } from "@snort/system";
|
||||
|
||||
import ProfileImage from "Element/ProfileImage";
|
||||
import DM from "Element/DM";
|
||||
@ -9,7 +9,7 @@ import NoteToSelf from "Element/NoteToSelf";
|
||||
import { useDmCache } from "Hooks/useDmsCache";
|
||||
import useLogin from "Hooks/useLogin";
|
||||
import WriteDm from "Element/WriteDm";
|
||||
import { unwrap } from "Util";
|
||||
import { unwrap } from "SnortUtils";
|
||||
|
||||
export default function DmWindow({ id }: { id: string }) {
|
||||
const pubKey = useLogin().publicKey;
|
||||
|
@ -1,9 +1,9 @@
|
||||
import "./FollowButton.css";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import { HexKey } from "@snort/nostr";
|
||||
import { HexKey } from "@snort/system";
|
||||
|
||||
import useEventPublisher from "Feed/EventPublisher";
|
||||
import { parseId } from "Util";
|
||||
import { parseId } from "SnortUtils";
|
||||
import useLogin from "Hooks/useLogin";
|
||||
import AsyncButton from "Element/AsyncButton";
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { ReactNode } from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import { HexKey } from "@snort/nostr";
|
||||
import { HexKey } from "@snort/system";
|
||||
|
||||
import useEventPublisher from "Feed/EventPublisher";
|
||||
import ProfilePreview from "Element/ProfilePreview";
|
||||
|
@ -13,7 +13,7 @@ import {
|
||||
NostrNestsRegex,
|
||||
WavlakeRegex,
|
||||
} from "Const";
|
||||
import { magnetURIDecode } from "Util";
|
||||
import { magnetURIDecode } from "SnortUtils";
|
||||
import SoundCloudEmbed from "Element/SoundCloudEmded";
|
||||
import MixCloudEmbed from "Element/MixCloudEmbed";
|
||||
import SpotifyEmbed from "Element/SpotifyEmbed";
|
||||
|
@ -6,7 +6,7 @@ import { useMemo } from "react";
|
||||
import SendSats from "Element/SendSats";
|
||||
import Icon from "Icons/Icon";
|
||||
import { useWallet } from "Wallet";
|
||||
import { decodeInvoice } from "Util";
|
||||
import { decodeInvoice } from "SnortUtils";
|
||||
|
||||
import messages from "./messages";
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
import { Magnet } from "Util";
|
||||
import { Magnet } from "SnortUtils";
|
||||
|
||||
interface MagnetLinkProps {
|
||||
magnet: Magnet;
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { useMemo } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { HexKey } from "@snort/nostr";
|
||||
import { HexKey } from "@snort/system";
|
||||
|
||||
import { useUserProfile } from "Hooks/useUserProfile";
|
||||
import { profileLink } from "Util";
|
||||
import { profileLink } from "SnortUtils";
|
||||
import { getDisplayName } from "Element/ProfileImage";
|
||||
|
||||
export default function Mention({ pubkey, relays }: { pubkey: HexKey; relays?: Array<string> | string }) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import { HexKey } from "@snort/nostr";
|
||||
import { HexKey } from "@snort/system";
|
||||
import useModeration from "Hooks/useModeration";
|
||||
|
||||
import messages from "./messages";
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import { HexKey } from "@snort/nostr";
|
||||
import { HexKey } from "@snort/system";
|
||||
import MuteButton from "Element/MuteButton";
|
||||
import ProfilePreview from "Element/ProfilePreview";
|
||||
import useModeration from "Hooks/useModeration";
|
||||
|
@ -1,5 +1,5 @@
|
||||
import "./Nip05.css";
|
||||
import { HexKey } from "@snort/nostr";
|
||||
import { HexKey } from "@snort/system";
|
||||
|
||||
import Icon from "Icons/Icon";
|
||||
import { useUserProfile } from "Hooks/useUserProfile";
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { useEffect, useMemo, useState, ChangeEvent } from "react";
|
||||
import { useIntl, FormattedMessage } from "react-intl";
|
||||
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 {
|
||||
ServiceProvider,
|
||||
@ -19,10 +19,10 @@ import SendSats from "Element/SendSats";
|
||||
import Copy from "Element/Copy";
|
||||
import { useUserProfile } from "Hooks/useUserProfile";
|
||||
import useEventPublisher from "Feed/EventPublisher";
|
||||
import { debounce } from "Util";
|
||||
import { debounce } from "SnortUtils";
|
||||
import useLogin from "Hooks/useLogin";
|
||||
import SnortServiceProvider from "Nip05/SnortServiceProvider";
|
||||
import { mapEventToProfile, UserCache } from "Cache";
|
||||
import { UserCache } from "Cache";
|
||||
|
||||
import messages from "./messages";
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
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 PageSpinner from "Element/PageSpinner";
|
||||
import Reveal from "Element/Reveal";
|
||||
@ -14,7 +14,7 @@ export default function NostrFileHeader({ link }: { link: NostrLink }) {
|
||||
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
|
||||
// todo: make use of hash
|
||||
// todo: use magnet or other links if present
|
||||
|
@ -1,8 +1,7 @@
|
||||
import { Link } from "react-router-dom";
|
||||
import { NostrPrefix } from "@snort/nostr";
|
||||
import { NostrPrefix, parseNostrLink } from "@snort/system";
|
||||
|
||||
import Mention from "Element/Mention";
|
||||
import { parseNostrLink } from "Util";
|
||||
import NoteQuote from "Element/NoteQuote";
|
||||
|
||||
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 { useInView } from "react-intersection-observer";
|
||||
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 Icon from "Icons/Icon";
|
||||
@ -19,14 +19,13 @@ import {
|
||||
normalizeReaction,
|
||||
Reaction,
|
||||
profileLink,
|
||||
} from "Util";
|
||||
} from "SnortUtils";
|
||||
import NoteFooter, { Translation } from "Element/NoteFooter";
|
||||
import NoteTime from "Element/NoteTime";
|
||||
import Reveal from "Element/Reveal";
|
||||
import useModeration from "Hooks/useModeration";
|
||||
import { UserCache } from "Cache/UserCache";
|
||||
import Poll from "Element/Poll";
|
||||
import { EventExt } from "System/EventExt";
|
||||
import useLogin from "Hooks/useLogin";
|
||||
import { setBookmarked, setPinned } from "Login";
|
||||
import { NostrFileElement } from "Element/NostrFileHeader";
|
||||
|
@ -1,11 +1,11 @@
|
||||
import "./NoteCreator.css";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
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 useEventPublisher from "Feed/EventPublisher";
|
||||
import { openFile } from "Util";
|
||||
import { openFile } from "SnortUtils";
|
||||
import Textarea from "Element/Textarea";
|
||||
import Modal from "Element/Modal";
|
||||
import ProfileImage from "Element/ProfileImage";
|
||||
@ -31,7 +31,6 @@ import { LNURL } from "LNURL";
|
||||
import messages from "./messages";
|
||||
import { ClipboardEventHandler, useState } from "react";
|
||||
import Spinner from "Icons/Spinner";
|
||||
import { EventBuilder } from "System";
|
||||
import { Menu, MenuItem } from "@szhsin/react-menu";
|
||||
import { LoginStore } from "Login";
|
||||
import { getCurrentSubscription } from "Subscription";
|
||||
|
@ -3,14 +3,14 @@ import { useSelector, useDispatch } from "react-redux";
|
||||
import { useIntl, FormattedMessage } from "react-intl";
|
||||
import { Menu, MenuItem } from "@szhsin/react-menu";
|
||||
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 Spinner from "Icons/Spinner";
|
||||
|
||||
import { formatShort } from "Number";
|
||||
import useEventPublisher from "Feed/EventPublisher";
|
||||
import { delay, normalizeReaction, unwrap } from "Util";
|
||||
import { delay, normalizeReaction, unwrap } from "SnortUtils";
|
||||
import { NoteCreator } from "Element/NoteCreator";
|
||||
import { ReBroadcaster } from "Element/ReBroadcaster";
|
||||
import Reactions from "Element/Reactions";
|
||||
|
@ -1,5 +1,5 @@
|
||||
import useEventFeed from "Feed/EventFeed";
|
||||
import { NostrLink } from "Util";
|
||||
import { NostrLink } from "@snort/system";
|
||||
import Note from "Element/Note";
|
||||
import PageSpinner from "Element/PageSpinner";
|
||||
|
||||
|
@ -1,14 +1,13 @@
|
||||
import "./NoteReaction.css";
|
||||
import { Link } from "react-router-dom";
|
||||
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 ProfileImage from "Element/ProfileImage";
|
||||
import { eventLink, hexToBech32 } from "Util";
|
||||
import { eventLink, hexToBech32 } from "SnortUtils";
|
||||
import NoteTime from "Element/NoteTime";
|
||||
import useModeration from "Hooks/useModeration";
|
||||
import { EventExt } from "System/EventExt";
|
||||
|
||||
export interface NoteReactionProps {
|
||||
data: TaggedRawEvent;
|
||||
@ -43,7 +42,7 @@ export default function NoteReaction(props: NoteReactionProps) {
|
||||
function extractRoot() {
|
||||
if (ev?.kind === EventKind.Repost && ev.content.length > 0 && ev.content !== "#[0]") {
|
||||
try {
|
||||
const r: RawEvent = JSON.parse(ev.content);
|
||||
const r: NostrEvent = JSON.parse(ev.content);
|
||||
return r as TaggedRawEvent;
|
||||
} catch (e) {
|
||||
console.error("Could not load reposted content", e);
|
||||
|
@ -1,7 +1,7 @@
|
||||
import "./NoteToSelf.css";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import { profileLink } from "Util";
|
||||
import { profileLink } from "SnortUtils";
|
||||
|
||||
import messages from "./messages";
|
||||
import Icon from "Icons/Icon";
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { TaggedRawEvent } from "@snort/nostr";
|
||||
import { TaggedRawEvent } from "@snort/system";
|
||||
import { useState } from "react";
|
||||
import { FormattedMessage, FormattedNumber, useIntl } from "react-intl";
|
||||
|
||||
@ -8,7 +8,7 @@ import useEventPublisher from "Feed/EventPublisher";
|
||||
import { useWallet } from "Wallet";
|
||||
import { useUserProfile } from "Hooks/useUserProfile";
|
||||
import { LNURL } from "LNURL";
|
||||
import { unwrap } from "Util";
|
||||
import { unwrap } from "SnortUtils";
|
||||
import { formatShort } from "Number";
|
||||
import Spinner from "Icons/Spinner";
|
||||
import SendSats from "Element/SendSats";
|
||||
|
@ -1,13 +1,12 @@
|
||||
import "./ProfileImage.css";
|
||||
|
||||
import React, { useMemo } from "react";
|
||||
import { HexKey, NostrPrefix } from "@snort/nostr";
|
||||
import { HexKey, NostrPrefix, MetadataCache } from "@snort/system";
|
||||
|
||||
import { useUserProfile } from "Hooks/useUserProfile";
|
||||
import { hexToBech32, profileLink } from "Util";
|
||||
import { hexToBech32, profileLink } from "SnortUtils";
|
||||
import Avatar from "Element/Avatar";
|
||||
import Nip05 from "Element/Nip05";
|
||||
import { MetadataCache } from "Cache";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
export interface ProfileImageProps {
|
||||
|
@ -4,7 +4,7 @@ import { ReactNode } from "react";
|
||||
import ProfileImage from "Element/ProfileImage";
|
||||
import FollowButton from "Element/FollowButton";
|
||||
import { useUserProfile } from "Hooks/useUserProfile";
|
||||
import { HexKey } from "@snort/nostr";
|
||||
import { HexKey } from "@snort/system";
|
||||
import { useInView } from "react-intersection-observer";
|
||||
|
||||
export interface ProfilePreviewProps {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import useImgProxy from "Hooks/useImgProxy";
|
||||
import { useEffect, useState } from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import { getUrlHostname } from "Util";
|
||||
import { getUrlHostname } from "SnortUtils";
|
||||
|
||||
interface ProxyImgProps extends React.DetailedHTMLProps<React.ImgHTMLAttributes<HTMLImageElement>, HTMLImageElement> {
|
||||
size?: number;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { RawEvent } from "@snort/nostr";
|
||||
import { NostrEvent } from "@snort/system";
|
||||
import { FormattedMessage, FormattedNumber } from "react-intl";
|
||||
|
||||
import { dedupe, hexToBech32, unixNow } from "Util";
|
||||
import { dedupe, hexToBech32, unixNow } from "SnortUtils";
|
||||
import FollowListBase from "Element/FollowListBase";
|
||||
import AsyncButton from "Element/AsyncButton";
|
||||
import { useWallet } from "Wallet";
|
||||
@ -13,7 +13,7 @@ import { LNURL } from "LNURL";
|
||||
import useEventPublisher from "Feed/EventPublisher";
|
||||
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 login = useLogin();
|
||||
const publisher = useEventPublisher();
|
||||
|
@ -2,7 +2,7 @@ import "./Reactions.css";
|
||||
|
||||
import { useState, useMemo, useEffect } from "react";
|
||||
import { useIntl, FormattedMessage } from "react-intl";
|
||||
import { TaggedRawEvent } from "@snort/nostr";
|
||||
import { TaggedRawEvent } from "@snort/system";
|
||||
|
||||
import { formatShort } from "Number";
|
||||
import Icon from "Icons/Icon";
|
||||
|
@ -2,11 +2,11 @@ import "./Relay.css";
|
||||
import { useMemo } from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { RelaySettings } from "@snort/nostr";
|
||||
import { RelaySettings } from "@snort/system";
|
||||
|
||||
import useRelayState from "Feed/RelayState";
|
||||
import { System } from "System";
|
||||
import { getRelayName, unixNowMs, unwrap } from "Util";
|
||||
import { System } from "index";
|
||||
import { getRelayName, unixNowMs, unwrap } from "SnortUtils";
|
||||
import useLogin from "Hooks/useLogin";
|
||||
import { setRelays } from "Login";
|
||||
import Icon from "Icons/Icon";
|
||||
@ -20,7 +20,9 @@ export interface RelayProps {
|
||||
export default function Relay(props: RelayProps) {
|
||||
const navigate = useNavigate();
|
||||
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 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 (
|
||||
<>
|
||||
<div className={`relay w-max`}>
|
||||
|
@ -2,7 +2,7 @@ import "./RelaysMetadata.css";
|
||||
import Nostrich from "nostrich.webp";
|
||||
import { useState } from "react";
|
||||
|
||||
import { FullRelaySettings } from "@snort/nostr";
|
||||
import { FullRelaySettings } from "@snort/system";
|
||||
import Icon from "Icons/Icon";
|
||||
|
||||
const RelayFavicon = ({ url }: { url: string }) => {
|
||||
|
@ -1,8 +1,9 @@
|
||||
import "./SendSats.css";
|
||||
import React, { useEffect, useMemo, useState } from "react";
|
||||
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 Icon from "Icons/Icon";
|
||||
import useEventPublisher from "Feed/EventPublisher";
|
||||
@ -11,11 +12,10 @@ import Modal from "Element/Modal";
|
||||
import QrCode from "Element/QrCode";
|
||||
import Copy from "Element/Copy";
|
||||
import { LNURL, LNURLError, LNURLErrorCode, LNURLInvoice, LNURLSuccessAction } from "LNURL";
|
||||
import { chunks, debounce } from "Util";
|
||||
import { chunks, debounce } from "SnortUtils";
|
||||
import { useWallet } from "Wallet";
|
||||
import useLogin from "Hooks/useLogin";
|
||||
import { generateRandomKey } from "Login";
|
||||
import { EventPublisher } from "System/EventPublisher";
|
||||
import { ZapPoolController } from "ZapPoolController";
|
||||
|
||||
import messages from "./messages";
|
||||
@ -124,7 +124,7 @@ export default function SendSats(props: SendSatsProps) {
|
||||
async function loadInvoice() {
|
||||
if (!amount || !handler || !publisher) return null;
|
||||
|
||||
let zap: RawEvent | undefined;
|
||||
let zap: NostrEvent | undefined;
|
||||
if (author && zapType !== ZapType.NonZap) {
|
||||
const relays = Object.keys(login.relays.item);
|
||||
|
||||
@ -133,7 +133,7 @@ export default function SendSats(props: SendSatsProps) {
|
||||
const randomKey = generateRandomKey();
|
||||
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", ""]));
|
||||
} else {
|
||||
zap = await publisher.zap(amount * 1000, author, relays, note, comment);
|
||||
|
@ -3,11 +3,11 @@ import { useState } from "react";
|
||||
|
||||
import useRelayState from "Feed/RelayState";
|
||||
import Tabs, { Tab } from "Element/Tabs";
|
||||
import { System } from "System";
|
||||
import { unwrap } from "Util";
|
||||
import { unwrap } from "SnortUtils";
|
||||
import useSystemState from "Hooks/useSystemState";
|
||||
import { RawReqFilter } from "@snort/nostr";
|
||||
import { ReqFilter } from "@snort/system";
|
||||
import { useCopy } from "useCopy";
|
||||
import { System } from "index";
|
||||
|
||||
function RelayInfo({ id }: { id: string }) {
|
||||
const state = useRelayState(id);
|
||||
@ -18,7 +18,7 @@ function Queries() {
|
||||
const qs = useSystemState();
|
||||
const { copy } = useCopy();
|
||||
|
||||
function countElements(filters: Array<RawReqFilter>) {
|
||||
function countElements(filters: Array<ReqFilter>) {
|
||||
let total = 0;
|
||||
for (const f of filters) {
|
||||
for (const v of Object.values(f)) {
|
||||
@ -30,15 +30,10 @@ function Queries() {
|
||||
return total;
|
||||
}
|
||||
|
||||
function queryInfo(q: {
|
||||
id: string;
|
||||
filters: Array<RawReqFilter>;
|
||||
closing: boolean;
|
||||
subFilters: Array<RawReqFilter>;
|
||||
}) {
|
||||
function queryInfo(q: { id: string; filters: Array<ReqFilter>; subFilters: Array<ReqFilter> }) {
|
||||
return (
|
||||
<div key={q.id}>
|
||||
{q.closing ? <s>{q.id}</s> : <>{q.id}</>}
|
||||
{q.id}
|
||||
<br />
|
||||
<span onClick={() => copy(JSON.stringify(q.filters))} className="pointer">
|
||||
Filters: {q.filters.length} ({countElements(q.filters)} elements)
|
||||
@ -66,8 +61,8 @@ const SubDebug = () => {
|
||||
return (
|
||||
<>
|
||||
<b>Connections:</b>
|
||||
{[...System.Sockets.keys()].map(k => (
|
||||
<RelayInfo id={k} />
|
||||
{System.Sockets.map(k => (
|
||||
<RelayInfo id={k.address} />
|
||||
))}
|
||||
</>
|
||||
);
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { HexKey, NostrPrefix } from "@snort/nostr";
|
||||
import { HexKey, NostrPrefix } from "@snort/system";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
import FollowListBase from "Element/FollowListBase";
|
||||
@ -7,7 +7,7 @@ import PageSpinner from "Element/PageSpinner";
|
||||
import NostrBandApi from "External/NostrBand";
|
||||
import SemisolDevApi from "External/SemisolDev";
|
||||
import useLogin from "Hooks/useLogin";
|
||||
import { hexToBech32 } from "Util";
|
||||
import { hexToBech32 } from "SnortUtils";
|
||||
|
||||
enum Provider {
|
||||
NostrBand = 1,
|
||||
|
@ -1,10 +1,10 @@
|
||||
import "./Text.css";
|
||||
import { useMemo } from "react";
|
||||
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 { eventLink, hexToBech32, splitByUrl, validateNostrLink } from "Util";
|
||||
import { eventLink, hexToBech32, splitByUrl } from "SnortUtils";
|
||||
import Invoice from "Element/Invoice";
|
||||
import Hashtag from "Element/Hashtag";
|
||||
import Mention from "Element/Mention";
|
||||
|
@ -4,12 +4,11 @@ import "./Textarea.css";
|
||||
import { useIntl } from "react-intl";
|
||||
import ReactTextareaAutocomplete from "@webscopeio/react-textarea-autocomplete";
|
||||
import TextareaAutosize from "react-textarea-autosize";
|
||||
import { NostrPrefix } from "@snort/nostr";
|
||||
import { NostrPrefix, MetadataCache } from "@snort/system";
|
||||
|
||||
import Avatar from "Element/Avatar";
|
||||
import Nip05 from "Element/Nip05";
|
||||
import { hexToBech32 } from "Util";
|
||||
import { MetadataCache } from "Cache";
|
||||
import { hexToBech32 } from "SnortUtils";
|
||||
import { UserCache } from "Cache/UserCache";
|
||||
|
||||
import messages from "./messages";
|
||||
|
@ -2,10 +2,17 @@ import "./Thread.css";
|
||||
import { useMemo, useState, ReactNode } from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
import { useNavigate, useLocation, Link, useParams } from "react-router-dom";
|
||||
import { TaggedRawEvent, u256, EventKind, NostrPrefix } from "@snort/nostr";
|
||||
import { EventExt, Thread as ThreadInfo } from "System/EventExt";
|
||||
import {
|
||||
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 Note from "Element/Note";
|
||||
import NoteGhost from "Element/NoteGhost";
|
||||
|
@ -2,10 +2,10 @@ import "./Timeline.css";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import { useCallback, useMemo } from "react";
|
||||
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 { dedupeByPubkey, findTag, tagFilterOfTextRepost } from "Util";
|
||||
import { dedupeByPubkey, findTag, tagFilterOfTextRepost } from "SnortUtils";
|
||||
import ProfileImage from "Element/ProfileImage";
|
||||
import useTimelineFeed, { TimelineFeed, TimelineSubject } from "Feed/TimelineFeed";
|
||||
import LoadMore from "Element/LoadMore";
|
||||
@ -143,7 +143,7 @@ const Timeline = (props: TimelineProps) => {
|
||||
)}
|
||||
{mainFeed.map(eventElement)}
|
||||
{(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" />
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { RawEvent, TaggedRawEvent } from "@snort/nostr";
|
||||
import { NostrEvent, TaggedRawEvent } from "@snort/system";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
import PageSpinner from "Element/PageSpinner";
|
||||
@ -7,7 +7,7 @@ import Note from "Element/Note";
|
||||
import NostrBandApi from "External/NostrBand";
|
||||
|
||||
export default function TrendingNotes() {
|
||||
const [posts, setPosts] = useState<Array<RawEvent>>();
|
||||
const [posts, setPosts] = useState<Array<NostrEvent>>();
|
||||
|
||||
async function loadTrendingNotes() {
|
||||
const api = new NostrBandApi();
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { HexKey } from "@snort/nostr";
|
||||
import { HexKey } from "@snort/system";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
import FollowListBase from "Element/FollowListBase";
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { MouseEvent } from "react";
|
||||
import { useNavigate, Link } from "react-router-dom";
|
||||
|
||||
import { HexKey } from "@snort/nostr";
|
||||
import { HexKey } from "@snort/system";
|
||||
|
||||
import { useUserProfile } from "Hooks/useUserProfile";
|
||||
import { profileLink } from "Util";
|
||||
import { profileLink } from "SnortUtils";
|
||||
|
||||
export default function Username({ pubkey, onLinkVisit }: { pubkey: HexKey; onLinkVisit(): void }) {
|
||||
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 Icon from "Icons/Icon";
|
||||
import Spinner from "Icons/Spinner";
|
||||
import { useState } from "react";
|
||||
import useFileUpload from "Upload";
|
||||
import { openFile } from "Util";
|
||||
import { openFile } from "SnortUtils";
|
||||
import Textarea from "./Textarea";
|
||||
|
||||
export default function WriteDm({ chatPubKey }: { chatPubKey: string }) {
|
||||
const [msg, setMsg] = useState("");
|
||||
const [sending, setSending] = useState(false);
|
||||
const [uploading, setUploading] = useState(false);
|
||||
const [otherEvents, setOtherEvents] = useState<Array<RawEvent>>([]);
|
||||
const [otherEvents, setOtherEvents] = useState<Array<NostrEvent>>([]);
|
||||
const [error, setError] = useState("");
|
||||
const publisher = useEventPublisher();
|
||||
const uploader = useFileUpload();
|
||||
|
@ -1,13 +1,13 @@
|
||||
import "./Zap.css";
|
||||
import { useMemo } from "react";
|
||||
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 Text from "Element/Text";
|
||||
import ProfileImage from "Element/ProfileImage";
|
||||
import { findTag } from "Util";
|
||||
import { findTag } from "SnortUtils";
|
||||
import { UserCache } from "Cache/UserCache";
|
||||
import useLogin from "Hooks/useLogin";
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import "./ZapButton.css";
|
||||
import { useState } from "react";
|
||||
import { HexKey } from "@snort/nostr";
|
||||
import { HexKey } from "@snort/system";
|
||||
|
||||
import { useUserProfile } from "Hooks/useUserProfile";
|
||||
import SendSats from "Element/SendSats";
|
||||
|
@ -1,12 +1,12 @@
|
||||
import "./ZapstrEmbed.css";
|
||||
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 ProfileImage from "Element/ProfileImage";
|
||||
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 cover = ev.tags.find(a => a[0] === "cover");
|
||||
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 {
|
||||
pubkey: string;
|
||||
@ -9,8 +9,8 @@ export interface TrendingUserResponse {
|
||||
}
|
||||
|
||||
export interface TrendingNote {
|
||||
event: RawEvent;
|
||||
author: RawEvent; // kind0 event
|
||||
event: NostrEvent;
|
||||
author: NostrEvent; // kind0 event
|
||||
}
|
||||
|
||||
export interface TrendingNoteResponse {
|
||||
|
@ -1,18 +1,18 @@
|
||||
type HookFn = () => void;
|
||||
type HookFn<TSnapshot> = (e?: TSnapshot) => void;
|
||||
|
||||
interface HookFilter {
|
||||
fn: HookFn;
|
||||
interface HookFilter<TSnapshot> {
|
||||
fn: HookFn<TSnapshot>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple React hookable store with manual change notifications
|
||||
*/
|
||||
export default abstract class ExternalStore<TSnapshot> {
|
||||
#hooks: Array<HookFilter> = [];
|
||||
#hooks: Array<HookFilter<TSnapshot>> = [];
|
||||
#snapshot: Readonly<TSnapshot> = {} as Readonly<TSnapshot>;
|
||||
#changed = true;
|
||||
|
||||
hook(fn: HookFn) {
|
||||
hook(fn: HookFn<TSnapshot>) {
|
||||
this.#hooks.push({
|
||||
fn,
|
||||
});
|
||||
@ -32,9 +32,9 @@ export default abstract class ExternalStore<TSnapshot> {
|
||||
return this.#snapshot;
|
||||
}
|
||||
|
||||
protected notifyChange() {
|
||||
protected notifyChange(sn?: TSnapshot) {
|
||||
this.#changed = true;
|
||||
this.#hooks.forEach(h => h.fn());
|
||||
this.#hooks.forEach(h => h.fn(sn));
|
||||
}
|
||||
|
||||
abstract takeSnapshot(): TSnapshot;
|
||||
|
@ -1,9 +1,7 @@
|
||||
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 { RequestBuilder } from "System";
|
||||
import { FlatNoteStore, ReplaceableNoteStore } from "System/NoteCollection";
|
||||
import { unwrap, findTag, chunks } from "SnortUtils";
|
||||
import useRequestBuilder from "Hooks/useRequestBuilder";
|
||||
|
||||
type BadgeAwards = {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { HexKey, Lists } from "@snort/nostr";
|
||||
import { HexKey, Lists } from "@snort/system";
|
||||
|
||||
import useNotelistSubscription from "Hooks/useNotelistSubscription";
|
||||
import useLogin from "Hooks/useLogin";
|
||||
|
@ -1,9 +1,8 @@
|
||||
import { useMemo } from "react";
|
||||
import { NostrPrefix } from "@snort/nostr";
|
||||
import { NostrPrefix, RequestBuilder, ReplaceableNoteStore, NostrLink } from "@snort/system";
|
||||
|
||||
import useRequestBuilder from "Hooks/useRequestBuilder";
|
||||
import { RequestBuilder, ReplaceableNoteStore } from "System";
|
||||
import { NostrLink, unwrap } from "Util";
|
||||
import { unwrap } from "SnortUtils";
|
||||
|
||||
export default function useEventFeed(link: NostrLink) {
|
||||
const sub = useMemo(() => {
|
||||
|
@ -1,12 +1,13 @@
|
||||
import { useMemo } from "react";
|
||||
import useLogin from "Hooks/useLogin";
|
||||
import { EventPublisher } from "System/EventPublisher";
|
||||
import { EventPublisher } from "@snort/system";
|
||||
import { System } from "index";
|
||||
|
||||
export default function useEventPublisher() {
|
||||
const { publicKey, privateKey } = useLogin();
|
||||
return useMemo(() => {
|
||||
if (publicKey) {
|
||||
return new EventPublisher(publicKey, privateKey);
|
||||
return new EventPublisher(System, publicKey, privateKey);
|
||||
}
|
||||
}, [publicKey, privateKey]);
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
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";
|
||||
|
||||
export default function useFollowersFeed(pubkey?: HexKey) {
|
||||
|
@ -1,7 +1,6 @@
|
||||
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 useLogin from "Hooks/useLogin";
|
||||
|
||||
|
@ -1,25 +1,27 @@
|
||||
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 useEventPublisher from "Feed/EventPublisher";
|
||||
import { getMutedKeys } from "Feed/MuteList";
|
||||
import useModeration from "Hooks/useModeration";
|
||||
import { FlatNoteStore, RequestBuilder } from "System";
|
||||
import useRequestBuilder from "Hooks/useRequestBuilder";
|
||||
import { DmCache } from "Cache";
|
||||
import useLogin from "Hooks/useLogin";
|
||||
import { addSubscription, setBlocked, setBookmarked, setFollows, setMuted, setPinned, setRelays, setTags } from "Login";
|
||||
import { SnortPubKey } from "Const";
|
||||
import { SubscriptionEvent } from "Subscription";
|
||||
import useRelaysFeedFollows from "./RelaysFeedFollows";
|
||||
import { UserRelays } from "Cache/UserRelayCache";
|
||||
|
||||
/**
|
||||
* Managed loading data for the current logged in user
|
||||
*/
|
||||
export default function useLoginFeed() {
|
||||
const login = useLogin();
|
||||
const { publicKey: pubKey, readNotifications } = login;
|
||||
const { publicKey: pubKey, readNotifications, follows } = login;
|
||||
const { isMuted } = useModeration();
|
||||
const publisher = useEventPublisher();
|
||||
|
||||
@ -39,6 +41,7 @@ export default function useLoginFeed() {
|
||||
.limit(1);
|
||||
|
||||
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().kinds([EventKind.DirectMessage]).tag("p", [pubKey]).since(dmSince);
|
||||
return b;
|
||||
@ -171,8 +174,12 @@ export default function useLoginFeed() {
|
||||
}
|
||||
}, [listsFeed]);
|
||||
|
||||
/*const fRelays = useRelaysFeedFollows(follows);
|
||||
useEffect(() => {
|
||||
FollowsRelays.bulkSet(fRelays).catch(console.error);
|
||||
}, [dispatch, fRelays]);*/
|
||||
UserRelays.buffer(follows.item).catch(console.error);
|
||||
}, [follows.item]);
|
||||
|
||||
const fRelays = useRelaysFeedFollows(follows.item);
|
||||
useEffect(() => {
|
||||
UserRelays.bulkSet(fRelays).catch(console.error);
|
||||
}, [fRelays]);
|
||||
}
|
||||
|
@ -1,8 +1,14 @@
|
||||
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 { ParameterizedReplaceableNoteStore, RequestBuilder } from "System";
|
||||
import { getNewest } from "SnortUtils";
|
||||
import useRequestBuilder from "Hooks/useRequestBuilder";
|
||||
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 useLogin from "Hooks/useLogin";
|
||||
|
||||
|
@ -1,18 +1,6 @@
|
||||
import { useSyncExternalStore } from "react";
|
||||
import { StateSnapshot } from "@snort/nostr";
|
||||
import { System } from "System";
|
||||
|
||||
const noop = () => {
|
||||
return () => undefined;
|
||||
};
|
||||
const noopState = (): StateSnapshot | undefined => {
|
||||
return undefined;
|
||||
};
|
||||
import { System } from "index";
|
||||
|
||||
export default function useRelayState(addr: string) {
|
||||
const c = System.Sockets.get(addr);
|
||||
return useSyncExternalStore<StateSnapshot | undefined>(
|
||||
c?.StatusHook.bind(c) ?? noop,
|
||||
c?.GetState.bind(c) ?? noopState
|
||||
);
|
||||
const c = System.Sockets.find(a => a.address === addr);
|
||||
return c;
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
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";
|
||||
|
||||
export default function useRelaysFeed(pubkey?: HexKey) {
|
||||
|
@ -1,25 +1,40 @@
|
||||
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 { PubkeyReplaceableNoteStore, RequestBuilder } from "System";
|
||||
import { sanitizeRelayUrl } from "SnortUtils";
|
||||
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 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;
|
||||
}, [pubkeys]);
|
||||
|
||||
function mapFromRelays(notes: Array<TaggedRawEvent>): UserRelayMap {
|
||||
return Object.fromEntries(
|
||||
notes.map(ev => {
|
||||
return [
|
||||
ev.pubkey,
|
||||
ev.tags
|
||||
function mapFromRelays(notes: Array<TaggedRawEvent>): Array<RelayList> {
|
||||
return notes.map(ev => {
|
||||
return {
|
||||
pubkey: ev.pubkey,
|
||||
created_at: ev.created_at,
|
||||
relays: ev.tags
|
||||
.map(a => {
|
||||
return {
|
||||
url: sanitizeRelayUrl(a[1]),
|
||||
@ -30,20 +45,20 @@ export default function useRelaysFeedFollows(pubkeys: HexKey[]): UserRelayMap {
|
||||
} as FullRelaySettings;
|
||||
})
|
||||
.filter(a => a.url !== undefined),
|
||||
];
|
||||
})
|
||||
);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function mapFromContactList(notes: Array<TaggedRawEvent>): UserRelayMap {
|
||||
return Object.fromEntries(
|
||||
notes.map(ev => {
|
||||
// instead of discarding the follow list we should also use it for follow graph
|
||||
function mapFromContactList(notes: Array<TaggedRawEvent>): Array<RelayList> {
|
||||
return notes.map(ev => {
|
||||
if (ev.content !== "" && ev.content !== "{}" && ev.content.startsWith("{") && ev.content.endsWith("}")) {
|
||||
try {
|
||||
const relays: Record<string, RelaySettings> = JSON.parse(ev.content);
|
||||
return [
|
||||
ev.pubkey,
|
||||
Object.entries(relays)
|
||||
return {
|
||||
pubkey: ev.pubkey,
|
||||
created_at: ev.created_at,
|
||||
relays: Object.entries(relays)
|
||||
.map(([k, v]) => {
|
||||
return {
|
||||
url: sanitizeRelayUrl(k),
|
||||
@ -51,23 +66,23 @@ export default function useRelaysFeedFollows(pubkeys: HexKey[]): UserRelayMap {
|
||||
} as FullRelaySettings;
|
||||
})
|
||||
.filter(a => a.url !== undefined),
|
||||
];
|
||||
};
|
||||
} catch {
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
return [ev.pubkey, []];
|
||||
})
|
||||
);
|
||||
return {
|
||||
pubkey: ev.pubkey,
|
||||
created_at: 0,
|
||||
relays: [],
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
const relays = useRequestBuilder<PubkeyReplaceableNoteStore>(PubkeyReplaceableNoteStore, sub);
|
||||
const notesRelays = relays.data?.filter(a => a.kind === EventKind.Relays) ?? [];
|
||||
const notesContactLists = relays.data?.filter(a => a.kind === EventKind.ContactList) ?? [];
|
||||
return useMemo(() => {
|
||||
return {
|
||||
...mapFromContactList(notesContactLists),
|
||||
...mapFromRelays(notesRelays),
|
||||
} as UserRelayMap;
|
||||
return [...mapFromContactList(notesContactLists), ...mapFromRelays(notesRelays)];
|
||||
}, [relays]);
|
||||
}
|
||||
|
@ -1,15 +1,22 @@
|
||||
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 { FlatNoteStore, RequestBuilder } from "System";
|
||||
import { appendDedupe } from "SnortUtils";
|
||||
import useRequestBuilder from "Hooks/useRequestBuilder";
|
||||
import useLogin from "Hooks/useLogin";
|
||||
|
||||
interface RelayTaggedEventId {
|
||||
id: u256;
|
||||
relay?: string;
|
||||
}
|
||||
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 [allEvents, setAllEvents] = useState<u256[]>([link.id]);
|
||||
const [allEvents, setAllEvents] = useState<Array<RelayTaggedEventId>>([linkTagged]);
|
||||
const pref = useLogin().preferences;
|
||||
|
||||
const sub = useMemo(() => {
|
||||
@ -17,7 +24,10 @@ export default function useThreadFeed(link: NostrLink) {
|
||||
sub.withOptions({
|
||||
leaveOpen: true,
|
||||
});
|
||||
sub.withFilter().ids(trackingEvents);
|
||||
const fTracking = sub.withFilter();
|
||||
for (const te of trackingEvents) {
|
||||
fTracking.id(te.id, te.relay);
|
||||
}
|
||||
sub
|
||||
.withFilter()
|
||||
.kinds(
|
||||
@ -25,7 +35,10 @@ export default function useThreadFeed(link: NostrLink) {
|
||||
? [EventKind.Reaction, EventKind.TextNote, EventKind.Repost, EventKind.ZapReceipt]
|
||||
: [EventKind.TextNote, EventKind.ZapReceipt, EventKind.Repost]
|
||||
)
|
||||
.tag("e", allEvents);
|
||||
.tag(
|
||||
"e",
|
||||
allEvents.map(a => a.id)
|
||||
);
|
||||
|
||||
if (trackingATags.length > 0) {
|
||||
const parsed = trackingATags.map(a => a.split(":"));
|
||||
@ -45,16 +58,27 @@ export default function useThreadFeed(link: NostrLink) {
|
||||
|
||||
useEffect(() => {
|
||||
setTrackingATags([]);
|
||||
setTrackingEvent([link.id]);
|
||||
setAllEvents([link.id]);
|
||||
setTrackingEvent([linkTagged]);
|
||||
setAllEvents([linkTagged]);
|
||||
}, [link.id]);
|
||||
|
||||
useEffect(() => {
|
||||
if (store.data) {
|
||||
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 eTagsMissing = eTags.filter(a => !mainNotes.some(b => b.id === a));
|
||||
const eTags = mainNotes
|
||||
.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));
|
||||
setAllEvents(s => appendDedupe(s, eTags));
|
||||
|
||||
|
@ -1,8 +1,7 @@
|
||||
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 { FlatNoteStore, RequestBuilder } from "System";
|
||||
import { unixNow, unwrap, tagFilterOfTextRepost } from "SnortUtils";
|
||||
import useRequestBuilder from "Hooks/useRequestBuilder";
|
||||
import useTimelineWindow from "Hooks/useTimelineWindow";
|
||||
import useLogin from "Hooks/useLogin";
|
||||
@ -134,7 +133,7 @@ export default function useTimelineFeed(subject: TimelineSubject, options: Timel
|
||||
}, [options.relay]);
|
||||
|
||||
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) {
|
||||
rb.withFilter()
|
||||
.kinds(
|
||||
|
@ -1,8 +1,7 @@
|
||||
import { useMemo } from "react";
|
||||
import { HexKey, EventKind } from "@snort/nostr";
|
||||
import { HexKey, EventKind, FlatNoteStore, RequestBuilder } from "@snort/system";
|
||||
|
||||
import { parseZap } from "Element/Zap";
|
||||
import { FlatNoteStore, RequestBuilder } from "System";
|
||||
import useRequestBuilder from "Hooks/useRequestBuilder";
|
||||
|
||||
export default function useZapsFeed(pubkey?: HexKey) {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import * as utils from "@noble/curves/abstract/utils";
|
||||
import * as base64 from "@protobufjs/base64";
|
||||
import { hmacSha256, unwrap } from "Util";
|
||||
import { hmacSha256, unwrap } from "SnortUtils";
|
||||
import useLogin from "Hooks/useLogin";
|
||||
|
||||
export interface ImgProxySettings {
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { useSyncExternalStore } from "react";
|
||||
import { HexKey, u256 } from "@snort/nostr";
|
||||
import { HexKey, u256 } from "@snort/system";
|
||||
|
||||
import { InteractionCache } from "Cache/EventInteractionCache";
|
||||
import { EventInteraction } from "Db";
|
||||
import { sha256, unwrap } from "Util";
|
||||
import { sha256, unwrap } from "SnortUtils";
|
||||
|
||||
export function useInteractionCache(pubkey?: HexKey, event?: u256) {
|
||||
const id = event && pubkey ? sha256(event + pubkey) : undefined;
|
||||
|
@ -4,7 +4,7 @@ import { EmailRegex, MnemonicRegex } from "Const";
|
||||
import { LoginStore } from "Login";
|
||||
import { generateBip39Entropy, entropyToPrivateKey } from "nip6";
|
||||
import { getNip05PubKey } from "Pages/LoginPage";
|
||||
import { bech32ToHex } from "Util";
|
||||
import { bech32ToHex } from "SnortUtils";
|
||||
|
||||
export default function useLoginHandler() {
|
||||
const { formatMessage } = useIntl();
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { HexKey } from "@snort/nostr";
|
||||
import { HexKey } from "@snort/system";
|
||||
import useEventPublisher from "Feed/EventPublisher";
|
||||
import useLogin from "Hooks/useLogin";
|
||||
import { setBlocked, setMuted } from "Login";
|
||||
import { appendDedupe } from "Util";
|
||||
import { appendDedupe } from "SnortUtils";
|
||||
|
||||
export default function useModeration() {
|
||||
const login = useLogin();
|
||||
|
@ -1,7 +1,13 @@
|
||||
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 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,40 +1,31 @@
|
||||
import { useSyncExternalStore } from "react";
|
||||
import { RequestBuilder, System } from "System";
|
||||
import { EmptySnapshot, NoteStore, StoreSnapshot } from "System/NoteCollection";
|
||||
import { unwrap } from "Util";
|
||||
import { RequestBuilder, EmptySnapshot, NoteStore, StoreSnapshot } from "@snort/system";
|
||||
import { unwrap } from "SnortUtils";
|
||||
import { System } from "index";
|
||||
|
||||
const useRequestBuilder = <TStore extends NoteStore, TSnapshot = ReturnType<TStore["getSnapshotData"]>>(
|
||||
type: { new (): TStore },
|
||||
rb: RequestBuilder | null,
|
||||
debounced?: number
|
||||
rb: RequestBuilder | null
|
||||
) => {
|
||||
const subscribe = (onChanged: () => void) => {
|
||||
const store = System.Query<TStore>(type, rb);
|
||||
let t: ReturnType<typeof setTimeout> | undefined;
|
||||
const release = store.hook(() => {
|
||||
if (!t) {
|
||||
t = setTimeout(() => {
|
||||
clearTimeout(t);
|
||||
t = undefined;
|
||||
onChanged();
|
||||
}, debounced ?? 500);
|
||||
}
|
||||
});
|
||||
|
||||
if (rb) {
|
||||
const q = System.Query<TStore>(type, rb);
|
||||
const release = q.feed.hook(onChanged);
|
||||
q.uncancel();
|
||||
return () => {
|
||||
if (rb?.id) {
|
||||
System.CancelQuery(rb.id);
|
||||
}
|
||||
q.cancel();
|
||||
release();
|
||||
};
|
||||
}
|
||||
return () => {
|
||||
// noop
|
||||
};
|
||||
};
|
||||
const getState = (): StoreSnapshot<TSnapshot> => {
|
||||
if (rb?.id) {
|
||||
const q = System.GetQuery(rb.id);
|
||||
const q = System.GetQuery(rb?.id ?? "");
|
||||
if (q) {
|
||||
return unwrap(q).feed?.snapshot as StoreSnapshot<TSnapshot>;
|
||||
}
|
||||
}
|
||||
return EmptySnapshot as StoreSnapshot<TSnapshot>;
|
||||
};
|
||||
return useSyncExternalStore<StoreSnapshot<TSnapshot>>(
|
||||
|
@ -1,9 +1,10 @@
|
||||
import { useSyncExternalStore } from "react";
|
||||
import { System, SystemSnapshot } from "System";
|
||||
import { SystemSnapshot } from "@snort/system";
|
||||
import { System } from "index";
|
||||
|
||||
export default function useSystemState() {
|
||||
return useSyncExternalStore<SystemSnapshot>(
|
||||
cb => System.hook(cb),
|
||||
() => System.getSnapshot()
|
||||
() => System.snapshot()
|
||||
);
|
||||
}
|
||||
|
@ -1,9 +1,8 @@
|
||||
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 { ProfileLoader } from "System/ProfileCache";
|
||||
import { ProfileLoader } from "index";
|
||||
|
||||
export function useUserProfile(pubKey?: HexKey): 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 { bech32ToText, unwrap } from "Util";
|
||||
import { bech32ToText, unwrap } from "SnortUtils";
|
||||
|
||||
const PayServiceTag = "payRequest";
|
||||
|
||||
@ -119,7 +119,7 @@ export class LNURL {
|
||||
* @param zap
|
||||
* @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 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 utils from "@noble/curves/abstract/utils";
|
||||
|
||||
import { DefaultRelays, SnortPubKey } from "Const";
|
||||
import { LoginStore, UserPreferences, LoginSession } from "Login";
|
||||
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 { EventPublisher } from "System/EventPublisher";
|
||||
import { System } from "index";
|
||||
|
||||
export function setRelays(state: LoginSession, relays: Record<string, RelaySettings>, createdAt: number) {
|
||||
if (state.relays.timestamp >= createdAt) {
|
||||
@ -78,7 +78,7 @@ export async function generateNewLogin() {
|
||||
}
|
||||
|
||||
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);
|
||||
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 { SubscriptionEvent } from "Subscription";
|
||||
|
||||
|
@ -1,12 +1,12 @@
|
||||
import * as secp from "@noble/curves/secp256k1";
|
||||
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 ExternalStore from "ExternalStore";
|
||||
import { LoginSession } from "Login";
|
||||
import { deepClone, sanitizeRelayUrl, unwrap } from "Util";
|
||||
import { deepClone, sanitizeRelayUrl, unwrap } from "SnortUtils";
|
||||
import { DefaultPreferences, UserPreferences } from "./Preferences";
|
||||
|
||||
const AccountStoreKey = "sessions";
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { EventKind } from "@snort/nostr";
|
||||
import { EventPublisher } from "System/EventPublisher";
|
||||
import { EventKind, EventPublisher } from "@snort/system";
|
||||
import { ServiceError, ServiceProvider } from "./ServiceProvider";
|
||||
|
||||
export interface ManageHandle {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import DnsOverHttpResolver from "dns-over-http-resolver";
|
||||
import { bech32ToHex } from "Util";
|
||||
import { bech32ToHex } from "SnortUtils";
|
||||
|
||||
const resolver = new DnsOverHttpResolver();
|
||||
interface NostrJson {
|
||||
|
@ -1,11 +1,9 @@
|
||||
import Nostrich from "nostrich.webp";
|
||||
|
||||
import { TaggedRawEvent } from "@snort/nostr";
|
||||
import { EventKind } from "@snort/nostr";
|
||||
import { MetadataCache } from "Cache";
|
||||
import { TaggedRawEvent, EventKind, MetadataCache } from "@snort/system";
|
||||
import { getDisplayName } from "Element/ProfileImage";
|
||||
import { MentionRegex } from "Const";
|
||||
import { tagFilterOfTextRepost, unwrap } from "Util";
|
||||
import { tagFilterOfTextRepost, unwrap } from "SnortUtils";
|
||||
import { UserCache } from "Cache/UserCache";
|
||||
import { LoginSession } from "Login";
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import DmWindow from "Element/DmWindow";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { bech32ToHex } from "Util";
|
||||
import { bech32ToHex } from "SnortUtils";
|
||||
|
||||
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 { FormattedMessage } from "react-intl";
|
||||
import { HexKey } from "@snort/nostr";
|
||||
import { HexKey } from "@snort/system";
|
||||
|
||||
import { ApiHost, KieranPubKey, SnortPubKey } from "Const";
|
||||
import ProfilePreview from "Element/ProfilePreview";
|
||||
import ZapButton from "Element/ZapButton";
|
||||
import { bech32ToHex } from "Util";
|
||||
import { bech32ToHex } from "SnortUtils";
|
||||
import SnortApi, { RevenueSplit, RevenueToday } from "SnortApi";
|
||||
|
||||
const Developers = [
|
||||
|
@ -9,21 +9,18 @@ import messages from "./messages";
|
||||
import Icon from "Icons/Icon";
|
||||
import { RootState } from "State/Store";
|
||||
import { setShow, reset } from "State/NoteCreator";
|
||||
import { System } from "System";
|
||||
import { System } from "index";
|
||||
import useLoginFeed from "Feed/LoginFeed";
|
||||
import { totalUnread } from "Pages/MessagesPage";
|
||||
import useModeration from "Hooks/useModeration";
|
||||
import { NoteCreator } from "Element/NoteCreator";
|
||||
import { db } from "Db";
|
||||
import useEventPublisher from "Feed/EventPublisher";
|
||||
import SubDebug from "Element/SubDebug";
|
||||
import { preload } from "Cache";
|
||||
import { useDmCache } from "Hooks/useDmsCache";
|
||||
import { mapPlanName } from "./subscribe";
|
||||
import useLogin from "Hooks/useLogin";
|
||||
import Avatar from "Element/Avatar";
|
||||
import { useUserProfile } from "Hooks/useUserProfile";
|
||||
import { profileLink } from "Util";
|
||||
import { profileLink } from "SnortUtils";
|
||||
import { getCurrentSubscription } from "Subscription";
|
||||
import Toaster from "Toaster";
|
||||
|
||||
@ -78,9 +75,9 @@ export default function Layout() {
|
||||
for (const [k, v] of Object.entries(relays.item)) {
|
||||
await System.ConnectToRelay(k, v);
|
||||
}
|
||||
for (const [k, c] of System.Sockets) {
|
||||
if (!relays.item[k] && !c.Ephemeral) {
|
||||
System.DisconnectRelay(k);
|
||||
for (const v of System.Sockets) {
|
||||
if (!relays.item[v.address] && !v.ephemeral) {
|
||||
System.DisconnectRelay(v.address);
|
||||
}
|
||||
}
|
||||
})();
|
||||
@ -112,29 +109,6 @@ export default function Layout() {
|
||||
};
|
||||
}, [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 (
|
||||
<div className={pageClass}>
|
||||
{!shouldHideHeader && (
|
||||
@ -170,7 +144,6 @@ export default function Layout() {
|
||||
<NoteCreator />
|
||||
</>
|
||||
)}
|
||||
{window.localStorage.getItem("debug") && <SubDebug />}
|
||||
<Toaster />
|
||||
</div>
|
||||
);
|
||||
|
@ -3,9 +3,9 @@ import "./LoginPage.css";
|
||||
import { CSSProperties, useEffect, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
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 useImgProxy from "Hooks/useImgProxy";
|
||||
import Icon from "Icons/Icon";
|
||||
|
@ -1,11 +1,11 @@
|
||||
import React, { useMemo, useState } from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
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 ProfileImage, { getDisplayName } from "Element/ProfileImage";
|
||||
import { dedupe, hexToBech32, unwrap } from "Util";
|
||||
import { dedupe, hexToBech32, unwrap } from "SnortUtils";
|
||||
import NoteToSelf from "Element/NoteToSelf";
|
||||
import useModeration from "Hooks/useModeration";
|
||||
import { useDmCache } from "Hooks/useDmsCache";
|
||||
@ -162,30 +162,30 @@ export function setLastReadDm(pk: HexKey) {
|
||||
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");
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
function unreadDms(dms: RawEvent[], myPubKey: HexKey, pk: HexKey) {
|
||||
function unreadDms(dms: NostrEvent[], myPubKey: HexKey, pk: HexKey) {
|
||||
if (pk === myPubKey) return 0;
|
||||
const lastRead = lastReadDm(pk);
|
||||
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) {
|
||||
return dmsInChat(
|
||||
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);
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
export function extractChats(dms: RawEvent[], myPubKey: HexKey) {
|
||||
export function extractChats(dms: NostrEvent[], myPubKey: HexKey) {
|
||||
const myDms = dmsForLogin(dms, myPubKey);
|
||||
const keys = myDms.map(a => [a.pubkey, dmTo(a)]).flat();
|
||||
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 { FormattedMessage } from "react-intl";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
|
||||
import Spinner from "Icons/Spinner";
|
||||
import { parseNostrLink, profileLink } from "Util";
|
||||
import { profileLink } from "SnortUtils";
|
||||
import { getNip05PubKey } from "Pages/LoginPage";
|
||||
import { System } from "System";
|
||||
|
||||
export default function NostrLinkHandler() {
|
||||
const params = useParams();
|
||||
@ -18,9 +17,6 @@ export default function NostrLinkHandler() {
|
||||
async function handleLink(link: string) {
|
||||
const nav = parseNostrLink(link);
|
||||
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) {
|
||||
navigate(`/e/${nav.encode()}`);
|
||||
} 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