mirror of
https://github.com/PrimalHQ/primal-web-app.git
synced 2024-10-03 02:10:55 +00:00
Check memebership status when logging in
This commit is contained in:
parent
2464df18f3
commit
7307bafbf5
@ -3,14 +3,14 @@ import { Progress } from '@kobalte/core';
|
|||||||
|
|
||||||
import styles from './Uploader.module.scss';
|
import styles from './Uploader.module.scss';
|
||||||
import { uploadServer } from '../../uploadSocket';
|
import { uploadServer } from '../../uploadSocket';
|
||||||
import { createStore, reconcile } from 'solid-js/store';
|
import { createStore } from 'solid-js/store';
|
||||||
import { NostrEOSE, NostrEvent, NostrEventContent, NostrEventType, NostrMediaUploaded } from '../../types/primal';
|
import { NostrEOSE, NostrEvent, NostrEventContent, NostrEventType, NostrMediaUploaded } from '../../types/primal';
|
||||||
import { readUploadTime, saveUploadTime } from '../../lib/localStore';
|
import { readUploadTime, saveUploadTime } from '../../lib/localStore';
|
||||||
import { startTimes, uploadMediaCancel, uploadMediaChunk, uploadMediaConfirm } from '../../lib/media';
|
import { startTimes, uploadMediaCancel, uploadMediaChunk, uploadMediaConfirm } from '../../lib/media';
|
||||||
import { sha256, uuidv4 } from '../../utils';
|
import { sha256, uuidv4 } from '../../utils';
|
||||||
import { Kind, uploadLimit } from '../../constants';
|
import { Kind, uploadLimit } from '../../constants';
|
||||||
import ButtonGhost from '../Buttons/ButtonGhost';
|
import ButtonGhost from '../Buttons/ButtonGhost';
|
||||||
import { isAccountVerified } from '../../lib/profile';
|
import { useAccountContext } from '../../contexts/AccountContext';
|
||||||
|
|
||||||
const MB = 1024 * 1024;
|
const MB = 1024 * 1024;
|
||||||
const maxParallelChunks = 5;
|
const maxParallelChunks = 5;
|
||||||
@ -44,6 +44,7 @@ const Uploader: Component<{
|
|||||||
onCancel?: () => void,
|
onCancel?: () => void,
|
||||||
onSuccsess?: (url: string) => void,
|
onSuccsess?: (url: string) => void,
|
||||||
}> = (props) => {
|
}> = (props) => {
|
||||||
|
const account = useAccountContext();
|
||||||
|
|
||||||
const [uploadState, setUploadState] = createStore<UploadState>({
|
const [uploadState, setUploadState] = createStore<UploadState>({
|
||||||
isUploading: false,
|
isUploading: false,
|
||||||
@ -113,7 +114,7 @@ const Uploader: Component<{
|
|||||||
});
|
});
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
calcUploadLimit(props.nip05);
|
calcUploadLimit(account?.membershipStatus.tier);
|
||||||
});
|
});
|
||||||
|
|
||||||
onCleanup(() => {
|
onCleanup(() => {
|
||||||
@ -127,21 +128,14 @@ const Uploader: Component<{
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const calcUploadLimit = (nip05: string | undefined) => {
|
const calcUploadLimit = (membershipTier: string | undefined) => {
|
||||||
|
|
||||||
if (!nip05) {
|
if (membershipTier === 'premium') {
|
||||||
setUploadState('uploadLimit', () => uploadLimit.regular);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
isAccountVerified(nip05).then(profile => {
|
|
||||||
if (profile && profile.pubkey === props.publicKey && nip05.endsWith && nip05.endsWith('primal.net')) {
|
|
||||||
setUploadState('uploadLimit', () => uploadLimit.premium);
|
setUploadState('uploadLimit', () => uploadLimit.premium);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setUploadState('uploadLimit', () => uploadLimit.regular);
|
setUploadState('uploadLimit', () => uploadLimit.regular);
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const subTo = (socket: WebSocket, subId: string, cb: (type: NostrEventType, subId: string, content?: NostrEventContent) => void ) => {
|
const subTo = (socket: WebSocket, subId: string, cb: (type: NostrEventType, subId: string, content?: NostrEventContent) => void ) => {
|
||||||
|
@ -17,11 +17,12 @@ import {
|
|||||||
NostrMutedContent,
|
NostrMutedContent,
|
||||||
NostrRelays,
|
NostrRelays,
|
||||||
NostrWindow,
|
NostrWindow,
|
||||||
|
MembershipStatus,
|
||||||
PrimalNote,
|
PrimalNote,
|
||||||
PrimalUser,
|
PrimalUser,
|
||||||
} from '../types/primal';
|
} from '../types/primal';
|
||||||
import { Kind, pinEncodePrefix, relayConnectingTimeout } from "../constants";
|
import { Kind, pinEncodePrefix, relayConnectingTimeout } from "../constants";
|
||||||
import { isConnected, refreshSocketListeners, removeSocketListeners, socket, subscribeTo, reset } from "../sockets";
|
import { isConnected, refreshSocketListeners, removeSocketListeners, socket, subscribeTo, reset, subTo } from "../sockets";
|
||||||
import { sendContacts, sendLike, sendMuteList, triggerImportEvents } from "../lib/notes";
|
import { sendContacts, sendLike, sendMuteList, triggerImportEvents } from "../lib/notes";
|
||||||
// @ts-ignore Bad types in nostr-tools
|
// @ts-ignore Bad types in nostr-tools
|
||||||
import { generatePrivateKey, Relay, getPublicKey as nostrGetPubkey, nip19 } from "nostr-tools";
|
import { generatePrivateKey, Relay, getPublicKey as nostrGetPubkey, nip19 } from "nostr-tools";
|
||||||
@ -38,6 +39,7 @@ import { logError, logInfo, logWarning } from "../lib/logger";
|
|||||||
import { useToastContext } from "../components/Toaster/Toaster";
|
import { useToastContext } from "../components/Toaster/Toaster";
|
||||||
import { useIntl } from "@cookbook/solid-intl";
|
import { useIntl } from "@cookbook/solid-intl";
|
||||||
import { account as tAccount } from "../translations";
|
import { account as tAccount } from "../translations";
|
||||||
|
import { getMembershipStatus } from "../lib/membership";
|
||||||
|
|
||||||
export type AccountContextStore = {
|
export type AccountContextStore = {
|
||||||
likes: string[],
|
likes: string[],
|
||||||
@ -68,6 +70,7 @@ export type AccountContextStore = {
|
|||||||
showGettingStarted: boolean,
|
showGettingStarted: boolean,
|
||||||
showLogin: boolean,
|
showLogin: boolean,
|
||||||
emojiHistory: EmojiOption[],
|
emojiHistory: EmojiOption[],
|
||||||
|
membershipStatus: MembershipStatus,
|
||||||
actions: {
|
actions: {
|
||||||
showNewNoteForm: () => void,
|
showNewNoteForm: () => void,
|
||||||
hideNewNoteForm: () => void,
|
hideNewNoteForm: () => void,
|
||||||
@ -126,6 +129,7 @@ const initialData = {
|
|||||||
showGettingStarted: false,
|
showGettingStarted: false,
|
||||||
showLogin: false,
|
showLogin: false,
|
||||||
emojiHistory: [],
|
emojiHistory: [],
|
||||||
|
membershipStatus: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AccountContext = createContext<AccountContextStore>();
|
export const AccountContext = createContext<AccountContextStore>();
|
||||||
@ -143,6 +147,8 @@ export function AccountProvider(props: { children: JSXElement }) {
|
|||||||
|
|
||||||
let connectedRelaysCopy: Relay[] = [];
|
let connectedRelaysCopy: Relay[] = [];
|
||||||
|
|
||||||
|
let membershipSocket: WebSocket | undefined;
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
checkNostrChange();
|
checkNostrChange();
|
||||||
@ -185,6 +191,49 @@ export function AccountProvider(props: { children: JSXElement }) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const openMembershipSocket = (onOpen: () => void) => {
|
||||||
|
membershipSocket = new WebSocket('wss://wallet.primal.net/v1');
|
||||||
|
|
||||||
|
membershipSocket.addEventListener('close', () => {
|
||||||
|
console.log('PREMIUM SOCKET CLOSED');
|
||||||
|
});
|
||||||
|
|
||||||
|
membershipSocket.addEventListener('open', () => {
|
||||||
|
console.log('PREMIUM SOCKET OPENED');
|
||||||
|
onOpen();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkMembershipStatus = () => {
|
||||||
|
openMembershipSocket(() => {
|
||||||
|
if (!membershipSocket || membershipSocket.readyState !== WebSocket.OPEN) return;
|
||||||
|
|
||||||
|
const subId = `ps_${APP_ID}`;
|
||||||
|
|
||||||
|
let gotEvent = false;
|
||||||
|
|
||||||
|
const unsub = subTo(membershipSocket, subId, (type, _, content) => {
|
||||||
|
if (type === 'EVENT') {
|
||||||
|
const status: MembershipStatus = JSON.parse(content?.content || '{}');
|
||||||
|
|
||||||
|
gotEvent = true;
|
||||||
|
updateStore('membershipStatus', () => ({ ...status }));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'EOSE') {
|
||||||
|
unsub();
|
||||||
|
membershipSocket?.close();
|
||||||
|
|
||||||
|
if (!gotEvent) {
|
||||||
|
updateStore('membershipStatus', () => ({ tier: 'none' }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
getMembershipStatus(store.publicKey, subId, membershipSocket);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const showGetStarted = () => {
|
const showGetStarted = () => {
|
||||||
updateStore('showGettingStarted', () => true);
|
updateStore('showGettingStarted', () => true);
|
||||||
}
|
}
|
||||||
@ -227,8 +276,17 @@ export function AccountProvider(props: { children: JSXElement }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const setPublicKey = (pubkey: string | undefined) => {
|
const setPublicKey = (pubkey: string | undefined) => {
|
||||||
|
|
||||||
|
if(pubkey && pubkey.length > 0) {
|
||||||
updateStore('publicKey', () => pubkey);
|
updateStore('publicKey', () => pubkey);
|
||||||
pubkey ? localStorage.setItem('pubkey', pubkey) : localStorage.removeItem('pubkey');
|
localStorage.setItem('pubkey', pubkey);
|
||||||
|
checkMembershipStatus();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
updateStore('publicKey', () => undefined);
|
||||||
|
localStorage.removeItem('pubkey');
|
||||||
|
}
|
||||||
|
|
||||||
updateStore('isKeyLookupDone', () => true);
|
updateStore('isKeyLookupDone', () => true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
38
src/lib/membership.ts
Normal file
38
src/lib/membership.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { Kind } from "../constants";
|
||||||
|
import { signEvent } from "./nostrAPI";
|
||||||
|
|
||||||
|
export const getMembershipStatus = async (pubkey: string | undefined, subId: string, socket: WebSocket) => {
|
||||||
|
if (!pubkey) return;
|
||||||
|
|
||||||
|
const event = {
|
||||||
|
kind: Kind.Settings,
|
||||||
|
tags: [['p', pubkey]],
|
||||||
|
created_at: Math.floor((new Date()).getTime() / 1000),
|
||||||
|
content: JSON.stringify({}),
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const signedNote = await signEvent(event);
|
||||||
|
|
||||||
|
const message = JSON.stringify([
|
||||||
|
"REQ",
|
||||||
|
subId,
|
||||||
|
{cache: ["membership_status", { event_from_user: signedNote }]},
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (socket) {
|
||||||
|
const e = new CustomEvent('send', { detail: { message, ws: socket }});
|
||||||
|
|
||||||
|
socket.send(message);
|
||||||
|
socket.dispatchEvent(e);
|
||||||
|
} else {
|
||||||
|
throw('no_socket');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (reason) {
|
||||||
|
console.error('Failed to upload: ', reason);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
@ -124,3 +124,21 @@ export const subscribeTo = (subId: string, cb: (type: NostrEventType, subId: str
|
|||||||
socket()?.removeEventListener('message', listener);
|
socket()?.removeEventListener('message', listener);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const subTo = (socket: WebSocket, subId: string, cb: (type: NostrEventType, subId: string, content?: NostrEventContent) => void ) => {
|
||||||
|
const listener = (event: MessageEvent) => {
|
||||||
|
const message: NostrEvent | NostrEOSE = JSON.parse(event.data);
|
||||||
|
const [type, subscriptionId, content] = message;
|
||||||
|
|
||||||
|
if (subId === subscriptionId) {
|
||||||
|
cb(type, subscriptionId, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
socket.addEventListener('message', listener);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
socket.removeEventListener('message', listener);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
12
src/types/primal.d.ts
vendored
12
src/types/primal.d.ts
vendored
@ -663,3 +663,15 @@ export type ContactsData = {
|
|||||||
tags: string[][],
|
tags: string[][],
|
||||||
following: string[],
|
following: string[],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type MembershipStatus = {
|
||||||
|
pubkey?: string,
|
||||||
|
tier?: string,
|
||||||
|
name?: string,
|
||||||
|
rename?: string,
|
||||||
|
nostr_address?: string,
|
||||||
|
lightning_address?: string,
|
||||||
|
primal_vip_profile?: string,
|
||||||
|
used_storage?: number,
|
||||||
|
expires_on?: number,
|
||||||
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user