mirror of
https://github.com/irislib/iris-messenger.git
synced 2024-09-20 01:56:33 +00:00
map pubkeys to internal user ids
This commit is contained in:
parent
6b386d413f
commit
296160bab3
@ -3,6 +3,7 @@ import { Helmet } from 'react-helmet';
|
||||
import { Router, RouterOnChangeArgs } from 'preact-router';
|
||||
|
||||
import Footer from './components/Footer';
|
||||
import Show from './components/helpers/Show';
|
||||
import MediaPlayer from './components/MediaPlayer';
|
||||
import Menu from './components/Menu';
|
||||
import Modal from './components/modal/Modal';
|
||||
@ -107,7 +108,9 @@ class Main extends Component<Props, ReactState> {
|
||||
return (
|
||||
<div className="flex justify-center">
|
||||
<section className="flex w-full max-w-screen-xl justify-between relative">
|
||||
{s.loggedIn ? <Menu /> : null}
|
||||
<Show when={s.loggedIn}>
|
||||
<Menu />
|
||||
</Show>
|
||||
<Helmet titleTemplate={titleTemplate} defaultTitle={defaultTitle}>
|
||||
<title>{title}</title>
|
||||
<meta name="description" content="Connecting People" />
|
||||
@ -153,7 +156,7 @@ class Main extends Component<Props, ReactState> {
|
||||
<Footer />
|
||||
</section>
|
||||
|
||||
{this.state.showLoginModal && (
|
||||
<Show when={s.showLoginModal}>
|
||||
<Modal
|
||||
centerVertically={true}
|
||||
showContainer={true}
|
||||
@ -161,7 +164,7 @@ class Main extends Component<Props, ReactState> {
|
||||
>
|
||||
<Login />
|
||||
</Modal>
|
||||
)}
|
||||
</Show>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -97,7 +97,7 @@ class MyAvatar extends Component<Props, State> {
|
||||
this.state.picture &&
|
||||
!this.state.hasError &&
|
||||
!this.props.hidePicture &&
|
||||
!SocialNetwork.blockedUsers.has(this.props.str as string);
|
||||
!SocialNetwork.isBlocked(this.props.str as string);
|
||||
const hasPictureStyle = hasPicture ? 'has-picture' : '';
|
||||
const showTooltip = this.props.showTooltip ? 'tooltip' : '';
|
||||
|
||||
|
@ -18,7 +18,7 @@ export default function Badge(props): JSX.Element | null {
|
||||
if (!hexAddress) {
|
||||
return null;
|
||||
}
|
||||
const following = SocialNetwork.followedByUser.get(myPub)?.has(hexAddress);
|
||||
const following = SocialNetwork.isFollowing(myPub, hexAddress);
|
||||
if (following) {
|
||||
return (
|
||||
<span class="mx-2 text-iris-blue tooltip" data-tip={t('following')}>
|
||||
|
@ -3,6 +3,7 @@ import { memo, useEffect, useState } from 'react';
|
||||
import AnimalName from '../AnimalName';
|
||||
import Key from '../nostr/Key';
|
||||
import SocialNetwork from '../nostr/SocialNetwork';
|
||||
import { ID } from '../nostr/UserIds';
|
||||
|
||||
import Badge from './Badge';
|
||||
|
||||
@ -26,7 +27,7 @@ const Name = (props: Props) => {
|
||||
let displayName;
|
||||
let isNameGenerated = false;
|
||||
|
||||
const profile = SocialNetwork.profiles.get(nostrAddr);
|
||||
const profile = SocialNetwork.profiles.get(ID(nostrAddr));
|
||||
// should we change SocialNetwork.getProfile() and use it here?
|
||||
if (profile) {
|
||||
name = profile.name?.trim().slice(0, 100) || '';
|
||||
|
@ -142,7 +142,7 @@ const EventComponent = (props: EventComponentProps) => {
|
||||
);
|
||||
}
|
||||
|
||||
if (SocialNetwork.blockedUsers.has(state.event.pubkey)) {
|
||||
if (SocialNetwork.isBlocked(state.event.pubkey)) {
|
||||
if (props.standalone || props.isQuote) {
|
||||
return (
|
||||
<div className="msg">
|
||||
|
@ -8,6 +8,7 @@ import Events from '../../nostr/Events';
|
||||
import Key from '../../nostr/Key';
|
||||
import PubSub, { Unsubscribe } from '../../nostr/PubSub';
|
||||
import SocialNetwork from '../../nostr/SocialNetwork';
|
||||
import { ID } from '../../nostr/UserIds';
|
||||
import { translate as t } from '../../translations/Translation.mjs';
|
||||
import Show from '../helpers/Show';
|
||||
|
||||
@ -188,7 +189,7 @@ class Feed extends BaseComponent<FeedProps, FeedState> {
|
||||
if (results.has(event.id)) {
|
||||
return;
|
||||
}
|
||||
if (SocialNetwork.blockedUsers.has(event.pubkey)) {
|
||||
if (SocialNetwork.isBlocked(event.pubkey)) {
|
||||
return;
|
||||
}
|
||||
const repliedMsg = Events.getEventReplyingTo(event);
|
||||
@ -197,7 +198,7 @@ class Feed extends BaseComponent<FeedProps, FeedState> {
|
||||
return;
|
||||
}
|
||||
const author = Events.db.by('id', repliedMsg)?.pubkey;
|
||||
if (author && SocialNetwork.blockedUsers.has(author)) {
|
||||
if (author && SocialNetwork.isBlocked(author)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -218,10 +219,11 @@ class Feed extends BaseComponent<FeedProps, FeedState> {
|
||||
});
|
||||
} else {
|
||||
this.unsub = this.getEvents(callback);
|
||||
const followCount = SocialNetwork.followedByUser.get(Key.getPubKey())?.size;
|
||||
const myId = ID(Key.getPubKey());
|
||||
const followCount = SocialNetwork.followedByUser.get(myId)?.size;
|
||||
const unsub = PubSub.subscribe({ authors: [Key.getPubKey()], kinds: [3] }, () => {
|
||||
// is this needed?
|
||||
if (followCount !== SocialNetwork.followedByUser.get(Key.getPubKey())?.size) {
|
||||
if (followCount !== SocialNetwork.followedByUser.get(myId)?.size) {
|
||||
unsub();
|
||||
this.subscribe();
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ import Events from '../../nostr/Events';
|
||||
import Key from '../../nostr/Key';
|
||||
import PubSub from '../../nostr/PubSub';
|
||||
import SocialNetwork from '../../nostr/SocialNetwork';
|
||||
import { ID, PUB } from '../../nostr/UserIds';
|
||||
|
||||
import BaseFeed from './BaseFeed';
|
||||
|
||||
@ -65,7 +66,9 @@ class Feed extends BaseFeed {
|
||||
|
||||
subscribeToFollows(since, callback) {
|
||||
const myPub = Key.getPubKey();
|
||||
const followedUsers = Array.from(SocialNetwork.followedByUser.get(myPub) || []);
|
||||
const followedUsers = Array.from(SocialNetwork.followedByUser.get(ID(myPub)) || []).map(
|
||||
(user) => PUB(user),
|
||||
);
|
||||
followedUsers.push(myPub);
|
||||
const filter = {
|
||||
kinds: [1, 6],
|
||||
@ -79,7 +82,7 @@ class Feed extends BaseFeed {
|
||||
return PubSub.subscribe(
|
||||
filter,
|
||||
(e) => {
|
||||
if (e.pubkey === myPub || SocialNetwork.followedByUser.get(myPub)?.has(e.pubkey)) {
|
||||
if (e.pubkey === myPub || SocialNetwork.isFollowing(myPub, e.pubkey)) {
|
||||
callback(e);
|
||||
}
|
||||
},
|
||||
|
@ -15,6 +15,7 @@ import Relays from './Relays';
|
||||
import Session from './Session';
|
||||
import SocialNetwork from './SocialNetwork';
|
||||
import SortedLimitedEventSet from './SortedLimitedEventSet';
|
||||
import { ID, PUB, UserId, UserIds } from './UserIds';
|
||||
|
||||
const startTime = Date.now() / 1000;
|
||||
|
||||
@ -27,6 +28,13 @@ const events = db.addCollection('events', {
|
||||
unique: ['id'],
|
||||
});
|
||||
|
||||
// print event db size
|
||||
/*
|
||||
setInterval(() => {
|
||||
console.log('event db size', events.count());
|
||||
}, 1000);
|
||||
*/
|
||||
|
||||
// add collections for Tags and Users?
|
||||
|
||||
let mutedNotes;
|
||||
@ -52,7 +60,7 @@ localState.get('dev').on((d) => {
|
||||
const Events = {
|
||||
DEFAULT_GLOBAL_FILTER,
|
||||
getEventHash,
|
||||
db: events,
|
||||
db: events, // TODO gb2 own indexing with Maps. easier to evict & understand what it actually does
|
||||
eventsMetaDb: new EventMetaStore(),
|
||||
seen: new Set<string>(),
|
||||
deletedEvents: new Set<string>(),
|
||||
@ -102,7 +110,7 @@ const Events = {
|
||||
if (
|
||||
event.created_at > startTime ||
|
||||
event.pubkey === myPub ||
|
||||
SocialNetwork.followedByUser.get(myPub)?.has(event.pubkey)
|
||||
SocialNetwork.isFollowing(myPub, event.pubkey)
|
||||
) {
|
||||
//Events.getEventById(id); // generates lots of subscriptions
|
||||
}
|
||||
@ -180,7 +188,7 @@ const Events = {
|
||||
this.insert(event);
|
||||
const myPub = Key.getPubKey();
|
||||
|
||||
if (event.pubkey === myPub || SocialNetwork.followedByUser.get(myPub)?.has(event.pubkey)) {
|
||||
if (event.pubkey === myPub || SocialNetwork.isFollowing(myPub, event.pubkey)) {
|
||||
LocalForage.loaded && LocalForage.saveProfilesAndFollows();
|
||||
}
|
||||
|
||||
@ -190,22 +198,25 @@ const Events = {
|
||||
const pub = tag[1];
|
||||
// ensure pub is hex
|
||||
if (pub.length === 64 && /^[0-9a-f]+$/.test(pub)) {
|
||||
SocialNetwork.addFollower(tag[1], event.pubkey);
|
||||
SocialNetwork.addFollower(ID(tag[1]), ID(event.pubkey));
|
||||
} else {
|
||||
// console.error('non-hex follow tag', tag, 'by', event.pubkey);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (SocialNetwork.followedByUser.has(event.pubkey)) {
|
||||
for (const previouslyFollowed of SocialNetwork.followedByUser.get(event.pubkey) || []) {
|
||||
if (!event.tags || !event.tags?.find((t) => t[0] === 'p' && t[1] === previouslyFollowed)) {
|
||||
SocialNetwork.removeFollower(previouslyFollowed, event.pubkey);
|
||||
if (SocialNetwork.followedByUser.has(ID(event.pubkey))) {
|
||||
for (const previouslyFollowed of SocialNetwork.followedByUser.get(ID(event.pubkey)) || []) {
|
||||
if (
|
||||
!event.tags ||
|
||||
!event.tags?.find((t) => t[0] === 'p' && t[1] === PUB(previouslyFollowed))
|
||||
) {
|
||||
SocialNetwork.removeFollower(previouslyFollowed, ID(event.pubkey));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (event.pubkey === myPub && event.tags?.length) {
|
||||
if ((SocialNetwork.followedByUser.get(myPub)?.size || 0) > 10) {
|
||||
if ((SocialNetwork.followedByUser.get(ID(myPub))?.size || 0) > 10) {
|
||||
localState.get('showFollowSuggestions').put(false);
|
||||
}
|
||||
}
|
||||
@ -241,7 +252,11 @@ const Events = {
|
||||
try {
|
||||
content = await Key.decrypt(event.content);
|
||||
const blockList = JSON.parse(content);
|
||||
SocialNetwork.blockedUsers = new Set(blockList);
|
||||
const set = new Set<UserId>();
|
||||
for (const id of blockList) {
|
||||
set.add(ID(id));
|
||||
}
|
||||
SocialNetwork.blockedUsers = set;
|
||||
} catch (e) {
|
||||
console.log('failed to parse your block list', content, event);
|
||||
}
|
||||
@ -275,7 +290,7 @@ const Events = {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const existing = SocialNetwork.profiles.get(event.pubkey);
|
||||
const existing = SocialNetwork.profiles.get(ID(event.pubkey));
|
||||
if (existing?.created_at >= event.created_at) {
|
||||
return false;
|
||||
}
|
||||
@ -291,13 +306,13 @@ const Events = {
|
||||
}
|
||||
profile.created_at = event.created_at;
|
||||
delete profile['nip05valid']; // not robust
|
||||
SocialNetwork.profiles.set(event.pubkey, profile);
|
||||
SocialNetwork.profiles.set(ID(event.pubkey), profile);
|
||||
const key = Key.toNostrBech32Address(event.pubkey, 'npub');
|
||||
FuzzySearch.add({
|
||||
key,
|
||||
name: profile.name,
|
||||
display_name: profile.display_name,
|
||||
followers: SocialNetwork.followersByUser.get(event.pubkey) ?? new Set(),
|
||||
followers: SocialNetwork.followersByUser.get(ID(event.pubkey)) ?? new Set(),
|
||||
});
|
||||
//}
|
||||
} catch (e) {
|
||||
@ -372,8 +387,12 @@ const Events = {
|
||||
if (globalFilter.maxFollowDistance && !!Key.getPubKey()) {
|
||||
if (!PubSub.subscribedAuthors.has(event.pubkey) && !PubSub.subscribedEventIds.has(event.id)) {
|
||||
// unless we specifically subscribed to the user or post, ignore long follow distance users
|
||||
if (SocialNetwork.followDistanceByUser.has(event.pubkey)) {
|
||||
const distance = SocialNetwork.followDistanceByUser.get(event.pubkey);
|
||||
if (!event.pubkey) {
|
||||
console.log('what', event);
|
||||
return false;
|
||||
}
|
||||
if (UserIds.has(event.pubkey) && SocialNetwork.getFollowDistance(event.pubkey)) {
|
||||
const distance = SocialNetwork.followDistanceByUser.get(ID(event.pubkey));
|
||||
if (distance && distance > globalFilter.maxFollowDistance) {
|
||||
// follow distance too high, reject
|
||||
return false;
|
||||
@ -381,7 +400,7 @@ const Events = {
|
||||
if (distance === globalFilter.maxFollowDistance) {
|
||||
// require at least 5 followers
|
||||
// TODO followers should be follow distance 2
|
||||
const followerCount = SocialNetwork.followersByUser.get(event.pubkey)?.size || 0;
|
||||
const followerCount = SocialNetwork.followersByUser.get(ID(event.pubkey))?.size || 0;
|
||||
if (
|
||||
followerCount <
|
||||
(globalFilter.minFollowersAtMaxDistance ||
|
||||
@ -449,7 +468,7 @@ const Events = {
|
||||
}
|
||||
// Accepting metadata so we still get their name. But should we instead save the name on our own list?
|
||||
// They might spam with 1 MB events and keep changing their name or something.
|
||||
if (SocialNetwork.blockedUsers.has(event.pubkey) && event.kind !== 0) {
|
||||
if (SocialNetwork.isBlocked(event.pubkey) && event.kind !== 0) {
|
||||
return false;
|
||||
}
|
||||
if (this.deletedEvents.has(event.id)) {
|
||||
@ -535,7 +554,7 @@ const Events = {
|
||||
// TODO since we're only querying relays since lastSeen, we need to store all beforeseen events and correctly query them on demand
|
||||
// otherwise feed will be incomplete
|
||||
if (saveToIdb) {
|
||||
const followDistance = SocialNetwork.followDistanceByUser.get(event.pubkey) || Infinity;
|
||||
const followDistance = SocialNetwork.followDistanceByUser.get(ID(event.pubkey)) || Infinity;
|
||||
if (followDistance <= 1) {
|
||||
// save all our own events and events from people we follow
|
||||
if (dev.indexedDbSave !== false) {
|
||||
@ -616,8 +635,8 @@ const Events = {
|
||||
if (event.pubkey !== myPub && event.tags?.some((tag) => tag[0] === 'p' && tag[1] === myPub)) {
|
||||
if (event.kind === 3) {
|
||||
// only notify if we know that they previously weren't following us
|
||||
const existingFollows = SocialNetwork.followedByUser.get(event.pubkey);
|
||||
if (!existingFollows || existingFollows.has(myPub)) {
|
||||
const existingFollows = SocialNetwork.followedByUser.get(ID(event.pubkey));
|
||||
if (!existingFollows || existingFollows.has(ID(myPub))) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -78,11 +78,11 @@ export default {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
getPubKey() {
|
||||
return this.key?.rpub;
|
||||
getPubKey(): string {
|
||||
return this.key?.rpub || '';
|
||||
},
|
||||
getPrivKey() {
|
||||
return this.key?.priv;
|
||||
getPrivKey(): string {
|
||||
return this.key?.priv || '';
|
||||
},
|
||||
encrypt: async function (data: string, pub?: string): Promise<string> {
|
||||
const k = this.key;
|
||||
|
@ -17,7 +17,7 @@ const getLatestByFollows = () => {
|
||||
latestByFollows.applyFind({ kind: 1 });
|
||||
latestByFollows.applySimpleSort('created_at', { desc: true });
|
||||
latestByFollows.applyWhere((event: Event) => {
|
||||
const distance = SocialNetwork.followDistanceByUser.get(event.pubkey) || Infinity;
|
||||
const distance = SocialNetwork.getFollowDistance(event.pubkey);
|
||||
return distance <= 1;
|
||||
});
|
||||
return latestByFollows;
|
||||
@ -73,7 +73,7 @@ export default {
|
||||
...Events.db.find({ kind: 0 }),
|
||||
];
|
||||
const followEvents = Events.db.find({ kind: 3 }).filter((e: Event) => {
|
||||
return e.pubkey === myPub || SocialNetwork.followedByUser.get(myPub)?.has(e.pubkey);
|
||||
return e.pubkey === myPub || SocialNetwork.isFollowing(myPub, e.pubkey);
|
||||
});
|
||||
const followEvents2 = [Events.db.findOne({ kind: 3, pubkey: myPub })];
|
||||
let size = 0;
|
||||
|
@ -13,6 +13,7 @@ import { Path } from './path';
|
||||
import PubSub from './PubSub';
|
||||
import Relays from './Relays';
|
||||
import SocialNetwork from './SocialNetwork';
|
||||
import { ID } from './UserIds';
|
||||
|
||||
try {
|
||||
localStorage.setItem('gunPeers', JSON.stringify({})); // quick fix to not connect gun
|
||||
@ -53,9 +54,10 @@ const Session = {
|
||||
},
|
||||
onLoggedIn() {
|
||||
const myPub = Key.getPubKey();
|
||||
SocialNetwork.followDistanceByUser.set(myPub, 0);
|
||||
SocialNetwork.followersByUser.set(myPub, new Set());
|
||||
SocialNetwork.usersByFollowDistance.set(0, new Set([myPub]));
|
||||
const myId = ID(myPub);
|
||||
SocialNetwork.followDistanceByUser.set(myId, 0);
|
||||
SocialNetwork.followersByUser.set(myId, new Set());
|
||||
SocialNetwork.usersByFollowDistance.set(0, new Set([myId]));
|
||||
const subscribe = (filters: Filter[], callback: (event: Event) => void): string => {
|
||||
const filter = filters[0];
|
||||
const key = filter['#d']?.[0];
|
||||
|
@ -4,26 +4,49 @@ import Events from './Events';
|
||||
import Key from './Key';
|
||||
import LocalForage from './LocalForage';
|
||||
import PubSub, { Unsubscribe } from './PubSub';
|
||||
import { ID, PUB, UserId } from './UserIds';
|
||||
|
||||
export default {
|
||||
followDistanceByUser: new Map<string, number>(),
|
||||
usersByFollowDistance: new Map<number, Set<string>>(),
|
||||
profiles: new Map<string, any>(), // JSON.parsed event.content of profiles
|
||||
followedByUser: new Map<string, Set<string>>(),
|
||||
followersByUser: new Map<string, Set<string>>(),
|
||||
blockedUsers: new Set<string>(),
|
||||
flaggedUsers: new Set<string>(),
|
||||
followDistanceByUser: new Map<UserId, number>(),
|
||||
usersByFollowDistance: new Map<number, Set<UserId>>(),
|
||||
profiles: new Map<UserId, any>(), // JSON.parsed event.content of profile events
|
||||
followedByUser: new Map<UserId, Set<UserId>>(),
|
||||
followersByUser: new Map<UserId, Set<UserId>>(),
|
||||
blockedUsers: new Set<UserId>(),
|
||||
flaggedUsers: new Set<UserId>(),
|
||||
|
||||
isFollowing: function (follower: string, followedUser: string): boolean {
|
||||
const followedUserId = ID(followedUser);
|
||||
const followerId = ID(follower);
|
||||
return !!this.followedByUser.get(followerId)?.has(followedUserId);
|
||||
},
|
||||
|
||||
isBlocked: function (blockedUser: string): boolean {
|
||||
const blockedUserId = ID(blockedUser);
|
||||
return this.blockedUsers.has(blockedUserId);
|
||||
},
|
||||
|
||||
getFollowDistance: function (user: string): number {
|
||||
const userId = ID(user);
|
||||
return this.followDistanceByUser.get(userId) || Infinity;
|
||||
},
|
||||
|
||||
setFollowed: function (followedUsers: string | string[], follow = true) {
|
||||
const myPub = Key.getPubKey();
|
||||
const myId = ID(myPub);
|
||||
|
||||
if (typeof followedUsers === 'string') {
|
||||
followedUsers = [followedUsers];
|
||||
}
|
||||
const myPub = Key.getPubKey();
|
||||
|
||||
followedUsers.forEach((followedUser) => {
|
||||
followedUser = Key.toNostrHexAddress(followedUser) || '';
|
||||
const followedUserId = ID(followedUser);
|
||||
|
||||
if (follow && followedUser && followedUser !== myPub) {
|
||||
this.addFollower(followedUser, myPub);
|
||||
this.addFollower(followedUserId, myId);
|
||||
} else {
|
||||
this.removeFollower(followedUser, myPub);
|
||||
this.removeFollower(followedUserId, myId);
|
||||
}
|
||||
});
|
||||
|
||||
@ -32,8 +55,9 @@ export default {
|
||||
const event = {
|
||||
kind: 3,
|
||||
content: existing?.content || '',
|
||||
tags: Array.from(this.followedByUser.get(myPub) || []).map((address: string) => {
|
||||
return ['p', address];
|
||||
tags: Array.from(this.followedByUser.get(myId) || []).map((id: number) => {
|
||||
const pubAddress = PUB(id);
|
||||
return ['p', pubAddress];
|
||||
}),
|
||||
};
|
||||
|
||||
@ -41,19 +65,19 @@ export default {
|
||||
},
|
||||
|
||||
setBlocked: function (blockedUser: string, block = true) {
|
||||
blockedUser = Key.toNostrHexAddress(blockedUser) || '';
|
||||
const myPub = Key.getPubKey();
|
||||
const blockedUserId = ID(blockedUser);
|
||||
const myId = ID(Key.getPubKey());
|
||||
|
||||
if (block) {
|
||||
this.blockedUsers.add(blockedUser);
|
||||
this.removeFollower(blockedUser, myPub);
|
||||
this.blockedUsers.add(blockedUserId);
|
||||
this.removeFollower(blockedUserId, myId);
|
||||
Events.directMessagesByUser.delete(blockedUser);
|
||||
} else {
|
||||
this.blockedUsers.delete(blockedUser);
|
||||
this.blockedUsers.delete(blockedUserId);
|
||||
}
|
||||
},
|
||||
|
||||
addUserByFollowDistance(distance: number, user: string) {
|
||||
addUserByFollowDistance(distance: number, user: UserId) {
|
||||
if (!this.usersByFollowDistance.has(distance)) {
|
||||
this.usersByFollowDistance.set(distance, new Set());
|
||||
}
|
||||
@ -61,7 +85,7 @@ export default {
|
||||
let unsub;
|
||||
// TODO subscribe once param?
|
||||
// eslint-disable-next-line prefer-const
|
||||
unsub = PubSub.subscribe({ authors: [user], kinds: [0] }, () => unsub?.(), true);
|
||||
unsub = PubSub.subscribe({ authors: [PUB(user)], kinds: [0] }, () => unsub?.(), true);
|
||||
}
|
||||
this.usersByFollowDistance.get(distance)?.add(user);
|
||||
// remove from higher distances
|
||||
@ -72,30 +96,24 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
addFollower: function (followedUser: string, follower: string) {
|
||||
if (followedUser.startsWith('npub')) {
|
||||
console.error('addFollower: followedUser is not a hex address', followedUser);
|
||||
followedUser = Key.toNostrHexAddress(followedUser) || '';
|
||||
addFollower: function (followedUser: UserId, follower: UserId) {
|
||||
if (typeof followedUser !== 'number' || typeof follower !== 'number') {
|
||||
throw new Error('Invalid user id');
|
||||
}
|
||||
if (follower.startsWith('npub')) {
|
||||
console.error('addFollower: follower is not a hex address', follower);
|
||||
follower = Key.toNostrHexAddress(follower) || '';
|
||||
}
|
||||
|
||||
if (!this.followersByUser.has(followedUser)) {
|
||||
this.followersByUser.set(followedUser, new Set<string>());
|
||||
this.followersByUser.set(followedUser, new Set<UserId>());
|
||||
}
|
||||
this.followersByUser.get(followedUser)?.add(follower);
|
||||
|
||||
if (!this.followedByUser.has(follower)) {
|
||||
this.followedByUser.set(follower, new Set<string>());
|
||||
this.followedByUser.set(follower, new Set<UserId>());
|
||||
}
|
||||
const myPub = Key.getPubKey();
|
||||
const myId = ID(Key.getPubKey());
|
||||
|
||||
let newFollowDistance;
|
||||
if (follower === myPub) {
|
||||
if (follower === myId) {
|
||||
// basically same as the next "else" block, but faster
|
||||
if (followedUser === myPub) {
|
||||
if (followedUser === myId) {
|
||||
newFollowDistance = 0; // self-follow
|
||||
} else {
|
||||
newFollowDistance = 1;
|
||||
@ -113,20 +131,20 @@ export default {
|
||||
}
|
||||
|
||||
this.followedByUser.get(follower)?.add(followedUser);
|
||||
if (followedUser === myPub) {
|
||||
if (followedUser === myId) {
|
||||
if (this.followersByUser.get(followedUser)?.size === 1) {
|
||||
localState.get('hasNostrFollowers').put(true);
|
||||
}
|
||||
}
|
||||
if (this.followedByUser.get(myPub)?.has(follower)) {
|
||||
if (!PubSub.subscribedAuthors.has(followedUser)) {
|
||||
if (this.followedByUser.get(myId)?.has(follower)) {
|
||||
if (!PubSub.subscribedAuthors.has(PUB(followedUser))) {
|
||||
setTimeout(() => {
|
||||
PubSub.subscribe({ authors: [followedUser] }, undefined, true);
|
||||
PubSub.subscribe({ authors: [PUB(followedUser)] }, undefined, true);
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
},
|
||||
removeFollower: function (unfollowedUser: string, follower: string) {
|
||||
removeFollower: function (unfollowedUser: UserId, follower: UserId) {
|
||||
this.followersByUser.get(unfollowedUser)?.delete(follower);
|
||||
this.followedByUser.get(follower)?.delete(unfollowedUser);
|
||||
|
||||
@ -154,26 +172,32 @@ export default {
|
||||
// if resulting followersByUser(u).size is 0, remove that user as well
|
||||
this.followDistanceByUser.delete(unfollowedUser);
|
||||
this.followersByUser.delete(unfollowedUser);
|
||||
PubSub.subscribedAuthors.delete(unfollowedUser);
|
||||
PubSub.subscribedAuthors.delete(PUB(unfollowedUser));
|
||||
}
|
||||
LocalForage.saveEvents();
|
||||
},
|
||||
// TODO subscription methods for followersByUser and followedByUser. and maybe messagesByTime. and replies
|
||||
followerCount: function (address: string) {
|
||||
return this.followersByUser.get(address)?.size ?? 0;
|
||||
const id = ID(address);
|
||||
return this.followersByUser.get(id)?.size ?? 0;
|
||||
},
|
||||
followedByFriendsCount: function (address: string) {
|
||||
let count = 0;
|
||||
const myPub = Key.getPubKey();
|
||||
for (const follower of this.followersByUser.get(address) ?? []) {
|
||||
if (this.followedByUser.get(myPub)?.has(follower)) {
|
||||
const myId = ID(Key.getPubKey());
|
||||
const id = ID(address);
|
||||
for (const follower of this.followersByUser.get(id) ?? []) {
|
||||
if (this.followedByUser.get(myId)?.has(follower)) {
|
||||
count++; // should we stop at 10?
|
||||
}
|
||||
}
|
||||
return count;
|
||||
},
|
||||
block: async function (address: string, isBlocked: boolean) {
|
||||
isBlocked ? this.blockedUsers.add(address) : this.blockedUsers.delete(address);
|
||||
if (isBlocked) {
|
||||
this.blockedUsers.add(ID(address));
|
||||
} else {
|
||||
this.blockedUsers.delete(ID(address));
|
||||
}
|
||||
let content: any = JSON.stringify(Array.from(this.blockedUsers));
|
||||
content = await Key.encrypt(content);
|
||||
Events.publish({
|
||||
@ -184,7 +208,11 @@ export default {
|
||||
});
|
||||
},
|
||||
flag: function (address: string, isFlagged: boolean) {
|
||||
isFlagged ? this.flaggedUsers.add(address) : this.flaggedUsers.delete(address);
|
||||
if (isFlagged) {
|
||||
this.flaggedUsers.add(ID(address));
|
||||
} else {
|
||||
this.flaggedUsers.delete(ID(address));
|
||||
}
|
||||
Events.publish({
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
@ -194,7 +222,13 @@ export default {
|
||||
},
|
||||
getBlockedUsers(cb?: (blocked: Set<string>) => void): Unsubscribe {
|
||||
const callback = () => {
|
||||
cb?.(this.blockedUsers);
|
||||
if (cb) {
|
||||
const set = new Set<string>();
|
||||
for (const id of this.blockedUsers) {
|
||||
set.add(PUB(id));
|
||||
}
|
||||
cb(set);
|
||||
}
|
||||
};
|
||||
callback();
|
||||
const myPub = Key.getPubKey();
|
||||
@ -204,7 +238,13 @@ export default {
|
||||
},
|
||||
getFlaggedUsers(cb?: (flagged: Set<string>) => void): Unsubscribe {
|
||||
const callback = () => {
|
||||
cb?.(this.flaggedUsers);
|
||||
if (cb) {
|
||||
const set = new Set<string>();
|
||||
for (const id of this.flaggedUsers) {
|
||||
set.add(PUB(id));
|
||||
}
|
||||
cb(set);
|
||||
}
|
||||
};
|
||||
callback();
|
||||
const myPub = Key.getPubKey();
|
||||
@ -216,33 +256,48 @@ export default {
|
||||
user: string,
|
||||
cb?: (followedUsers: Set<string>) => void,
|
||||
): Unsubscribe {
|
||||
const userId = ID(user);
|
||||
const callback = () => {
|
||||
cb?.(this.followedByUser.get(user) ?? new Set());
|
||||
if (cb) {
|
||||
const set = new Set<string>();
|
||||
for (const id of this.followedByUser.get(userId) || []) {
|
||||
set.add(PUB(id));
|
||||
}
|
||||
cb(set);
|
||||
}
|
||||
};
|
||||
this.followedByUser.has(user) && callback();
|
||||
this.followedByUser.has(userId) && callback();
|
||||
return PubSub.subscribe({ kinds: [3], authors: [user] }, callback);
|
||||
},
|
||||
getFollowersByUser: function (
|
||||
address: string,
|
||||
cb?: (followers: Set<string>) => void,
|
||||
): Unsubscribe {
|
||||
const userId = ID(address);
|
||||
const callback = () => {
|
||||
cb?.(this.followersByUser.get(address) ?? new Set());
|
||||
if (cb) {
|
||||
const set = new Set<string>();
|
||||
for (const id of this.followersByUser.get(userId) || []) {
|
||||
set.add(PUB(id));
|
||||
}
|
||||
cb(set);
|
||||
}
|
||||
};
|
||||
this.followersByUser.has(address) && callback();
|
||||
this.followersByUser.has(userId) && callback();
|
||||
return PubSub.subscribe({ kinds: [3], '#p': [address] }, callback); // TODO this doesn't fire when a user is unfollowed
|
||||
},
|
||||
// TODO param "proxyFirst" to skip relays if http proxy responds quickly
|
||||
getProfile(
|
||||
address,
|
||||
address: string,
|
||||
cb?: (profile: any, address: string) => void,
|
||||
verifyNip05 = false,
|
||||
): Unsubscribe {
|
||||
const id = ID(address);
|
||||
const callback = () => {
|
||||
cb?.(this.profiles.get(address), address);
|
||||
cb?.(this.profiles.get(id), address);
|
||||
};
|
||||
|
||||
const profile = this.profiles.get(address);
|
||||
const profile = this.profiles.get(id);
|
||||
// TODO subscribe & callback
|
||||
if (profile) {
|
||||
callback();
|
||||
@ -250,7 +305,7 @@ export default {
|
||||
Key.verifyNip05Address(profile.nip05, address).then((isValid) => {
|
||||
console.log('NIP05 address is valid?', isValid, profile.nip05, address);
|
||||
profile.nip05valid = isValid;
|
||||
this.profiles.set(address, profile);
|
||||
this.profiles.set(id, profile);
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
43
src/js/nostr/UserIds.ts
Normal file
43
src/js/nostr/UserIds.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import Key from './Key';
|
||||
|
||||
// should this be a class instead? convert all strings to internal representation, enable comparison
|
||||
export type UserId = number;
|
||||
|
||||
// save space by mapping pubkeys to internal user ids
|
||||
export class UserIds {
|
||||
static pubKeyToUserId = new Map<string, number>();
|
||||
static UserIdToPubKey = new Map<number, string>();
|
||||
static currentUserId = 0;
|
||||
|
||||
static id(pubKey: string): number {
|
||||
if (pubKey.startsWith('npub')) {
|
||||
pubKey = Key.toNostrHexAddress(pubKey) || '';
|
||||
if (!pubKey) {
|
||||
throw new Error('addFollower: invalid pubKey ' + pubKey);
|
||||
}
|
||||
}
|
||||
const existing = UserIds.pubKeyToUserId.get(pubKey);
|
||||
if (existing) {
|
||||
return existing;
|
||||
}
|
||||
const newId = UserIds.currentUserId++;
|
||||
UserIds.pubKeyToUserId.set(pubKey, newId);
|
||||
UserIds.UserIdToPubKey.set(newId, pubKey);
|
||||
return newId;
|
||||
}
|
||||
|
||||
static pub(id: number): string {
|
||||
const pub = UserIds.UserIdToPubKey.get(id);
|
||||
if (!pub) {
|
||||
throw new Error('pub: invalid id ' + id);
|
||||
}
|
||||
return pub;
|
||||
}
|
||||
|
||||
static has(pubKey: string): boolean {
|
||||
return UserIds.pubKeyToUserId.has(pubKey);
|
||||
}
|
||||
}
|
||||
|
||||
export const PUB = UserIds.pub;
|
||||
export const ID = UserIds.id;
|
@ -226,7 +226,7 @@ class Profile extends View {
|
||||
<b>${this.state.followerCount}</b> ${t('followers')}
|
||||
</a>
|
||||
</div>
|
||||
${SocialNetwork.followedByUser.get(this.state.hexPub)?.has(Key.getPubKey())
|
||||
${SocialNetwork.isFollowing(this.state.hexPub, Key.getPubKey())
|
||||
? html` <div><small>${t('follows_you')}</small></div> `
|
||||
: ''}
|
||||
</div>
|
||||
|
@ -117,7 +117,7 @@ class PrivateChat extends PureComponent<PrivateChatProps, PrivateChatState> {
|
||||
if (
|
||||
this.chat &&
|
||||
!this.chat.uuid &&
|
||||
Key.toNostrHexAddress(this.props.id) !== Key.getPubKey()()
|
||||
Key.toNostrHexAddress(this.props.id) !== Key.getPubKey()
|
||||
) {
|
||||
if ($('.msg.our').length && !$('.msg.their').length && !this.chat.theirMsgsLastSeenTime) {
|
||||
$('#not-seen-by-them').slideDown();
|
||||
|
Loading…
Reference in New Issue
Block a user