Skip to content

Commit

Permalink
Merge remote-tracking branch 'refs/remotes/origin/main'
Browse files Browse the repository at this point in the history
  • Loading branch information
chmac committed Aug 11, 2024
2 parents 2997638 + 98e7bef commit 0d41cce
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 118 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"scripts": {
"start": "parcel --no-cache src/index.html",
"build": "yarn build:parcel && yarn build:copy-public",
"build:parcel": "parcel build",
"build:parcel": "parcel build --no-scope-hoist",
"build:copy-public": "cp public/* dist/",
"prettier": "prettier --write src/",
"deploy:build": "parcel build --public-url /nostr-map",
Expand Down Expand Up @@ -38,4 +38,4 @@
"@parcel/resolver-default": {
"packageExports": true
}
}
}
188 changes: 95 additions & 93 deletions src/map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ import {
CONTENT_MAXIMUM_LENGTH,
CONTENT_MINIMUM_LENGTH,
PANEL_CONTAINER_ID,
PLUS_CODE_TAG_KEY,
} from "./constants";
import { hasPrivateKey } from "./nostr/keys";
import { createNote } from "./nostr/notes";
import { _initRelays } from "./nostr/relays";
import { subscribe } from "./nostr/subscribe";
import { getMetadataEvent, subscribe } from "./nostr/subscribe";
import { startUserOnboarding } from "./onboarding";
import { Note } from "./types";
import { Note, NostrEvent, Kind30398Event, MetadataEvent } from "./types";
import { getProfileFromEvent, getTagFirstValueFromEvent } from "./nostr/utils";

const map = L.map("map", {
zoomControl: false,
Expand Down Expand Up @@ -156,121 +158,121 @@ globalThis.addEventListener("popstate", (event) => {
globalThis.document.location.reload();
});

function generateDatetimeFromNote(note: Note): string {
const { createdAt } = note;
function generateDatetimeFromEvent(event: Kind30398Event): string {
const createdAt =
parseInt(
getTagFirstValueFromEvent({
event,
tag: "original_created_at",
}) ?? "0"
) || 0;
const date = new Date(createdAt * 1000);

return date.toLocaleString();
}

function generateLinkFromNote(note: Note): string {
const { authorName, authorTrustrootsUsername, authorTripHoppingUserId } =
note;
if (authorTrustrootsUsername.length > 3) {
if (authorName.length > 1) {
return ` <a href="https://www.trustroots.org/profile/${authorTrustrootsUsername}" target="_blank">${authorName}</a>`;
function generateLinkFromMetadataEvent(event: MetadataEvent): string {
const profile = getProfileFromEvent({ event });

const { name, trustrootsUsername, tripHoppingUserId } = profile;
if (trustrootsUsername.length > 3) {
if (name.length > 1) {
return ` <a href="https://www.trustroots.org/profile/${trustrootsUsername}" target="_blank">${name}</a>`;
}
return ` <a href="https://www.trustroots.org/profile/${authorTrustrootsUsername}" target="_blank">${authorTrustrootsUsername}</a>`;
return ` <a href="https://www.trustroots.org/profile/${trustrootsUsername}" target="_blank">${trustrootsUsername}</a>`;
}

if (authorTripHoppingUserId.length > 3) {
if (authorName.length > 1) {
return ` <a href="https://www.triphopping.com/profile/${authorTripHoppingUserId}" target="_blank">${authorName}</a>`;
if (tripHoppingUserId.length > 3) {
if (name.length > 1) {
return ` <a href="https://www.triphopping.com/profile/${tripHoppingUserId}" target="_blank">${name}</a>`;
}
return ` <a href="https://www.triphopping.com/profile/${authorTripHoppingUserId}" target="_blank">${authorTripHoppingUserId.slice(
return ` <a href="https://www.triphopping.com/profile/${tripHoppingUserId}" target="_blank">${tripHoppingUserId.slice(
0,
5
)}</a>`;
}
return "";
}

function generateMapContentFromNotes(notes: Note[]) {
const lines = notes.reduce((existingLines, note) => {
const link = generateLinkFromNote(note);
const datetime = generateDatetimeFromNote(note);
const noteContent = `${note.content}${link} ${datetime}`;
return existingLines.concat(noteContent);
}, [] as string[]);
const content = lines.join("<br />");
return content;
function generateMapContentFromEvent(
event: Kind30398Event,
metadataEvent?: MetadataEvent
) {
const link = metadataEvent
? generateLinkFromMetadataEvent(metadataEvent)
: "";
const datetime = generateDatetimeFromEvent(event);
const noteContent = `${event.content}${link} ${datetime}`;
return noteContent;
}

// todo: needs to be DRYed up
function generateChatContentFromNotes(notes: Note[]) {
const lines = notes.reduce((existingLines, note) => {
const link = generateLinkFromNote(note);
const datetime = generateDatetimeFromNote(note);
const noteContent = `${datetime}, ${link}: ${note.content}`;
return existingLines.concat(noteContent);
}, [] as string[]);
const content = lines.join("<br />");
return content;
function generateChatContentFromNotes(
event: Kind30398Event,
metadataEvent: MetadataEvent
) {
const link = generateLinkFromMetadataEvent(metadataEvent);
const datetime = generateDatetimeFromEvent(event);
const noteContent = `${datetime}, ${link}: ${event.content}`;
return noteContent;
}

function addNoteToMap(note: Note) {
let existing = plusCodesWithPopupsAndNotes[note.plusCode];
function addNoteToMap(event: Kind30398Event) {
const plusCode =
getTagFirstValueFromEvent({
event,
tag: PLUS_CODE_TAG_KEY,
}) ?? "";

const decodedCoords = decode(plusCode);
const { resolution: res, longitude: cLong, latitude: cLat } = decodedCoords!;

let color;
let fillColor;
const hitchWikiYellow = "#F3DA71";
const hitchWikiYellowLight = "#FFFBEE";
const trGreen = "#12B591";

if (event.pubkey === HITCHMAPS_AUTHOR_PUBLIC_KEY) {
color = hitchWikiYellow;
fillColor = hitchWikiYellowLight;
} else {
color = trGreen;
}

if (existing) {
const popup = existing.popup;
const marker = L.circleMarker([cLat, cLong], {
...circleMarker,
color: color,
fillColor: fillColor,
}); // Create marker with decoded coordinates
marker.addTo(map);

// When using multiple NOSTR relays, deduplicate the notes by ID to ensure
// that we don't show the same note multiple times.
const noteAlreadyOnTheMap = existing.notes.find((n) => n.id === note.id);
if (typeof noteAlreadyOnTheMap !== "undefined") {
return;
}
marker.on(
"click",
async (markerClickEvent) =>
await populateAndOpenPopup(markerClickEvent, event)
);
}

const notes = [...existing.notes, note];
popup.setContent(generateMapContentFromNotes(notes));
} else {
const decodedCoords = decode(note.plusCode);
const {
resolution: res,
longitude: cLong,
latitude: cLat,
} = decodedCoords!;

let color;
let fillColor;
const hitchWikiYellow = "#F3DA71";
const hitchWikiYellowLight = "#FFFBEE";
const trGreen = "#12B591";

if (note.authorPublicKey === HITCHMAPS_AUTHOR_PUBLIC_KEY) {
color = hitchWikiYellow;
fillColor = hitchWikiYellowLight;
} else {
color = trGreen;
}
async function populateAndOpenPopup(
markerClickEvent: L.LeafletEvent,
kind30398Event: Kind30398Event
) {
const marker = markerClickEvent.target as L.Marker;

const marker = L.circleMarker([cLat, cLong], {
...circleMarker,
color: color,
fillColor: fillColor,
}); // Create marker with decoded coordinates
marker.addTo(map);

const contentMap = generateMapContentFromNotes([note]);
const contentChat = generateChatContentFromNotes([note]);

//todo: rename addNoteToMap and other map
console.log(note);
const geochatNotes = document.getElementById(
"geochat-notes"
) as HTMLElement;
const li = document.createElement("li");
li.innerHTML = contentChat;
geochatNotes.appendChild(li);

const popup = L.popup().setContent(contentMap);
marker.bindPopup(popup);
marker.on("click", () => marker.openPopup());
plusCodesWithPopupsAndNotes[note.plusCode] = {
popup,
notes: [note],
};
}
const authorPubkey = getTagFirstValueFromEvent({
event: kind30398Event,
tag: "p",
});
const metadataEvent = await getMetadataEvent(authorPubkey);
if (!metadataEvent)
console.warn(
`Could not get metadata event for "${kind30398Event.content}"`
);
const contentMap = generateMapContentFromEvent(kind30398Event, metadataEvent);
const popup = L.popup().setContent(contentMap);
marker.bindPopup(popup);
marker.openPopup();
}

function createPopupHtml(createNoteCallback) {
Expand Down Expand Up @@ -325,6 +327,6 @@ const mapStartup = async () => {
L.DomUtil.addClass(badge, "hide");
L.DomUtil.removeClass(badge, "show");
await _initRelays();
subscribe({ onNoteReceived: addNoteToMap });
subscribe({ onEventReceived: addNoteToMap });
};
mapStartup();
70 changes: 48 additions & 22 deletions src/nostr/subscribe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,14 @@ import {
PLUS_CODE_TAG_KEY,
TRUSTED_VALIDATION_PUBKEYS,
} from "../constants";
import { NostrEvent, Note, Profile } from "../types";
import { _query } from "./relays";
import {
Kind30398Event,
MetadataEvent,
NostrEvent,
Note,
Profile,
} from "../types";
import { _initRelays, _query } from "./relays";
import {
doesStringPassSanitisation,
getProfileFromEvent,
Expand Down Expand Up @@ -85,18 +91,26 @@ const eventToNote = ({
};
};

const metadataEvents = new nostrify.NCache({ max: 1000 });

export async function getMetadataEvent(pubkey) {
const events = await metadataEvents.query([{ authors: [pubkey] }]);
if (events.length === 0) return;
else return events[0] as MetadataEvent;
}

type SubscribeParams = {
/** The public key of the user to fetch events for, or undefined to fetch events from all users */
publicKey?: string;
/** The maximum number of notes to fetch
* @default 200
*/
limit?: number;
onNoteReceived: (note: Note) => void;
onEventReceived: (event: Kind30398Event) => void;
};
export const subscribe = async ({
publicKey,
onNoteReceived,
onEventReceived: onEventReceived,
limit = 200,
}: SubscribeParams) => {
console.log("#qnvvsm nostr/subscribe", publicKey);
Expand All @@ -119,9 +133,9 @@ export const subscribe = async ({
: eventsBaseFilter;
const eventsFilterWithLimit = { ...eventsFilter, limit };

const noteEventsQueue: NostrEvent[] = [];
const noteEventsQueue: Kind30398Event[] = [];

const onNoteEvent = (event: NostrEvent) => {
const onNoteEvent = (event: Kind30398Event) => {
// if (isDev()) console.log("#gITVd2 gotNoteEvent", event);

if (
Expand Down Expand Up @@ -160,7 +174,26 @@ export const subscribe = async ({
authors,
};

const onProfileEvent = (event: NostrEvent) => {
noteEventsQueue.forEach((event) => onEventReceived(event));
backgroundProfileFetching(profileFilter);
backgroundNoteEventsFetching(onEventReceived);
};

async function backgroundNoteEventsFetching(onEventReceived) {
const relayPool = await _initRelays();
const filter = {
kinds: [MAP_NOTE_REPOST_KIND],
"#L": ["open-location-code"],
since: Math.floor(Date.now() / 1000),
authors: TRUSTED_VALIDATION_PUBKEYS,
};
for await (const msg of relayPool.req([filter])) {
if (msg[0] === "EVENT") onEventReceived(msg[2] as Kind30398Event);
}
}

async function backgroundProfileFetching(profileFilter) {
const onProfileEvent = (event: MetadataEvent) => {
// if (isDev()) console.log("#zD1Iau got profile event", event);

const profile = getProfileFromEvent({ event });
Expand All @@ -177,19 +210,12 @@ export const subscribe = async ({
return;
}

profiles[publicKey] = profile;
};

await _query({
filters: [profileFilter],
onEvent: onProfileEvent,
});

// NOTE: At this point we should have fetched all the stored events, and all
// the profiles of the authors of all of those events
const notes = noteEventsQueue.map((event) =>
eventToNote({ event, profiles })
);
metadataEvents.add(event);

notes.forEach((note) => onNoteReceived(note));
};
// profiles[publicKey] = profile;
};
const relayPool = await _initRelays();
for await (const msg of relayPool.req([profileFilter])) {
if (msg[0] === "EVENT") onProfileEvent(msg[2] as MetadataEvent);
}
}
2 changes: 1 addition & 1 deletion src/nostr/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export const getProfileFromEvent = ({
}: {
event: NostrEvent;
}): Profile => {
if (event.kind !== Kind.Metadata) {
if (event?.kind !== Kind.Metadata) {
throw new Error("#pC5T6P Trying to get profile from non metadata event");
}

Expand Down
8 changes: 8 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ export type UnsignedEvent = Omit<
"created_at" | "pubkey" | "id" | "sig"
>;

export type Kind30398Event = NostrEvent & {
kind: 30398;
};

export type MetadataEvent = NostrEvent & {
kind: 0;
};

export type Note = {
id: string;
plusCode: string;
Expand Down

0 comments on commit 0d41cce

Please sign in to comment.