Compare commits
5 Commits
v0.5.0
...
4e76aee818
Author | SHA1 | Date | |
---|---|---|---|
4e76aee818
|
|||
e400debde2
|
|||
21c6be9195
|
|||
63ca7fbbfc
|
|||
26b5249283
|
25
public/.well-known/apple-app-site-association
Normal file
25
public/.well-known/apple-app-site-association
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"applinks": {
|
||||||
|
"apps": [],
|
||||||
|
"details": [
|
||||||
|
{
|
||||||
|
"appIDs": [
|
||||||
|
"24VGVR4CHC.io.nostrlabs.zap-stream"
|
||||||
|
],
|
||||||
|
"paths": [
|
||||||
|
"*"
|
||||||
|
],
|
||||||
|
"components": [
|
||||||
|
{
|
||||||
|
"/": "/*"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"webcredentials": {
|
||||||
|
"apps": [
|
||||||
|
"24VGVR4CHC.io.nostrlabs.zap-stream"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
14
public/.well-known/assetlinks.json
Normal file
14
public/.well-known/assetlinks.json
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"relation": [
|
||||||
|
"delegate_permission/common.handle_all_urls"
|
||||||
|
],
|
||||||
|
"target": {
|
||||||
|
"namespace": "android_app",
|
||||||
|
"package_name": "io.nostrlabs.zap_stream_flutter",
|
||||||
|
"sha256_cert_fingerprints": [
|
||||||
|
"6F:87:59:1F:55:29:82:75:F5:C0:D4:22:34:D5:68:DA:79:03:06:31:16:63:A8:28:04:27:D8:34:0A:8F:95:8A"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
@ -54,3 +54,5 @@ function loadWhitelist() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const WHITELIST: Array<string> | undefined = loadWhitelist();
|
export const WHITELIST: Array<string> | undefined = loadWhitelist();
|
||||||
|
|
||||||
|
export const NIP5_DOMAIN: string = import.meta.env.VITE_NIP5_DOMAIN || "zap.stream";
|
@ -3,19 +3,43 @@ import { FormattedMessage } from "react-intl";
|
|||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { getName } from "../profile";
|
import { getName } from "../profile";
|
||||||
|
|
||||||
import { StreamState } from "@/const";
|
import { NIP5_DOMAIN, StreamState } from "@/const";
|
||||||
import useImgProxy from "@/hooks/img-proxy";
|
import useImgProxy from "@/hooks/img-proxy";
|
||||||
import { formatSats } from "@/number";
|
import { formatSats } from "@/number";
|
||||||
import { extractStreamInfo, getHost, profileLink } from "@/utils";
|
import { extractStreamInfo, getHost, profileLink } from "@/utils";
|
||||||
import { useUserProfile } from "@snort/system-react";
|
import { useUserProfile } from "@snort/system-react";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Avatar } from "../avatar";
|
import { Avatar } from "../avatar";
|
||||||
import Logo from "../logo";
|
import Logo from "../logo";
|
||||||
import { useContentWarning } from "../nsfw";
|
import { useContentWarning } from "../nsfw";
|
||||||
import PillOpaque from "../pill-opaque";
|
import PillOpaque from "../pill-opaque";
|
||||||
import { RelativeTime } from "../relative-time";
|
import { RelativeTime } from "../relative-time";
|
||||||
import { StatePill } from "../state-pill";
|
import { StatePill } from "../state-pill";
|
||||||
|
import { NostrJson } from "@snort/shared";
|
||||||
|
|
||||||
|
const nameCache = new Map<string, NostrJson>();
|
||||||
|
async function fetchNostrAddresByPubkey(pubkey: string, domain: string, timeout = 2_000): Promise<NostrJson | undefined> {
|
||||||
|
if (!pubkey || !domain) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const cacheKey = `${pubkey}@${domain}`;
|
||||||
|
if (nameCache.has(cacheKey)) {
|
||||||
|
return nameCache.get(cacheKey);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const res = await fetch(`https://${domain}/.well-known/nostr.json?pubkey=${pubkey}`, {
|
||||||
|
signal: AbortSignal.timeout(timeout),
|
||||||
|
});
|
||||||
|
const ret = (await res.json()) as NostrJson;
|
||||||
|
nameCache.set(cacheKey, ret);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
} catch {
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
export function StreamTile({
|
export function StreamTile({
|
||||||
ev,
|
ev,
|
||||||
@ -34,11 +58,26 @@ export function StreamTile({
|
|||||||
}) {
|
}) {
|
||||||
const { title, image, status, participants, contentWarning, recording, ends } = extractStreamInfo(ev);
|
const { title, image, status, participants, contentWarning, recording, ends } = extractStreamInfo(ev);
|
||||||
const host = getHost(ev);
|
const host = getHost(ev);
|
||||||
|
const link = NostrLink.fromEvent(ev);
|
||||||
const hostProfile = useUserProfile(host);
|
const hostProfile = useUserProfile(host);
|
||||||
const isGrownUp = useContentWarning();
|
const isGrownUp = useContentWarning();
|
||||||
const { proxy } = useImgProxy();
|
const { proxy } = useImgProxy();
|
||||||
|
const [videoLink, setVideoLink] = useState(`/${link.encode()}`)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (status === StreamState.Live) {
|
||||||
|
fetchNostrAddresByPubkey(host, NIP5_DOMAIN).then((h) => {
|
||||||
|
if (h) {
|
||||||
|
const names = Object.entries(h.names);
|
||||||
|
if (names.length > 0) {
|
||||||
|
setVideoLink(`/${names[0][0]}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [status, videoLink]);
|
||||||
|
|
||||||
const link = NostrLink.fromEvent(ev);
|
|
||||||
const [hasImg, setHasImage] = useState((image?.length ?? 0) > 0 || (recording?.length ?? 0) > 0);
|
const [hasImg, setHasImage] = useState((image?.length ?? 0) > 0 || (recording?.length ?? 0) > 0);
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -47,7 +86,7 @@ export function StreamTile({
|
|||||||
"flex-row": style === "list",
|
"flex-row": style === "list",
|
||||||
})}>
|
})}>
|
||||||
<Link
|
<Link
|
||||||
to={`/${link.encode()}`}
|
to={videoLink}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
{
|
{
|
||||||
"blur transition": contentWarning,
|
"blur transition": contentWarning,
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
|
import { NIP5_DOMAIN } from "@/const";
|
||||||
import { fetchNip05Pubkey } from "@snort/shared";
|
import { fetchNip05Pubkey } from "@snort/shared";
|
||||||
import { NostrLink, NostrPrefix, tryParseNostrLink } from "@snort/system";
|
import { NostrEvent, NostrLink, NostrPrefix, tryParseNostrLink } from "@snort/system";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
|
|
||||||
export function useStreamLink() {
|
export function useStreamLink(evPreload?: NostrEvent) {
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const [link, setLink] = useState<NostrLink>();
|
const [link, setLink] = useState<NostrLink | undefined>(evPreload ? NostrLink.fromEvent(evPreload) : undefined);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (params.id) {
|
if (params.id) {
|
||||||
@ -13,7 +14,7 @@ export function useStreamLink() {
|
|||||||
if (parsedLink) {
|
if (parsedLink) {
|
||||||
setLink(parsedLink);
|
setLink(parsedLink);
|
||||||
} else {
|
} else {
|
||||||
const [handle, domain] = (params.id.includes("@") ? params.id : `${params.id}@zap.stream`).split("@");
|
const [handle, domain] = (params.id.includes("@") ? params.id : `${params.id}@${NIP5_DOMAIN}`).split("@");
|
||||||
fetchNip05Pubkey(handle, domain).then(d => {
|
fetchNip05Pubkey(handle, domain).then(d => {
|
||||||
if (d) {
|
if (d) {
|
||||||
setLink(new NostrLink(NostrPrefix.PublicKey, d));
|
setLink(new NostrLink(NostrPrefix.PublicKey, d));
|
||||||
|
@ -14,7 +14,7 @@ import { ShortPage } from "./short";
|
|||||||
export function LinkHandler() {
|
export function LinkHandler() {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const evPreload = getEventFromLocationState(location.state);
|
const evPreload = getEventFromLocationState(location.state);
|
||||||
const link = useStreamLink();
|
const link = useStreamLink(evPreload);
|
||||||
const layoutContext = useLayout();
|
const layoutContext = useLayout();
|
||||||
|
|
||||||
if (!link) return;
|
if (!link) return;
|
||||||
|
Reference in New Issue
Block a user