Check memebership status when logging in

This commit is contained in:
Bojan Mojsilovic 2024-03-08 14:05:41 +01:00
parent 2464df18f3
commit 7307bafbf5
5 changed files with 137 additions and 17 deletions

View File

@ -3,14 +3,14 @@ import { Progress } from '@kobalte/core';
import styles from './Uploader.module.scss';
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 { readUploadTime, saveUploadTime } from '../../lib/localStore';
import { startTimes, uploadMediaCancel, uploadMediaChunk, uploadMediaConfirm } from '../../lib/media';
import { sha256, uuidv4 } from '../../utils';
import { Kind, uploadLimit } from '../../constants';
import ButtonGhost from '../Buttons/ButtonGhost';
import { isAccountVerified } from '../../lib/profile';
import { useAccountContext } from '../../contexts/AccountContext';
const MB = 1024 * 1024;
const maxParallelChunks = 5;
@ -44,6 +44,7 @@ const Uploader: Component<{
onCancel?: () => void,
onSuccsess?: (url: string) => void,
}> = (props) => {
const account = useAccountContext();
const [uploadState, setUploadState] = createStore<UploadState>({
isUploading: false,
@ -113,7 +114,7 @@ const Uploader: Component<{
});
createEffect(() => {
calcUploadLimit(props.nip05);
calcUploadLimit(account?.membershipStatus.tier);
});
onCleanup(() => {
@ -127,21 +128,14 @@ const Uploader: Component<{
}
});
const calcUploadLimit = (nip05: string | undefined) => {
const calcUploadLimit = (membershipTier: string | undefined) => {
if (!nip05) {
setUploadState('uploadLimit', () => uploadLimit.regular);
if (membershipTier === 'premium') {
setUploadState('uploadLimit', () => uploadLimit.premium);
return;
}
isAccountVerified(nip05).then(profile => {
if (profile && profile.pubkey === props.publicKey && nip05.endsWith && nip05.endsWith('primal.net')) {
setUploadState('uploadLimit', () => uploadLimit.premium);
return;
}
setUploadState('uploadLimit', () => uploadLimit.regular);
});
setUploadState('uploadLimit', () => uploadLimit.regular);
};
const subTo = (socket: WebSocket, subId: string, cb: (type: NostrEventType, subId: string, content?: NostrEventContent) => void ) => {

View File

@ -17,11 +17,12 @@ import {
NostrMutedContent,
NostrRelays,
NostrWindow,
MembershipStatus,
PrimalNote,
PrimalUser,
} from '../types/primal';
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";
// @ts-ignore Bad types in 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 { useIntl } from "@cookbook/solid-intl";
import { account as tAccount } from "../translations";
import { getMembershipStatus } from "../lib/membership";
export type AccountContextStore = {
likes: string[],
@ -68,6 +70,7 @@ export type AccountContextStore = {
showGettingStarted: boolean,
showLogin: boolean,
emojiHistory: EmojiOption[],
membershipStatus: MembershipStatus,
actions: {
showNewNoteForm: () => void,
hideNewNoteForm: () => void,
@ -126,6 +129,7 @@ const initialData = {
showGettingStarted: false,
showLogin: false,
emojiHistory: [],
membershipStatus: {},
};
export const AccountContext = createContext<AccountContextStore>();
@ -143,6 +147,8 @@ export function AccountProvider(props: { children: JSXElement }) {
let connectedRelaysCopy: Relay[] = [];
let membershipSocket: WebSocket | undefined;
onMount(() => {
setInterval(() => {
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 = () => {
updateStore('showGettingStarted', () => true);
}
@ -227,8 +276,17 @@ export function AccountProvider(props: { children: JSXElement }) {
}
const setPublicKey = (pubkey: string | undefined) => {
updateStore('publicKey', () => pubkey);
pubkey ? localStorage.setItem('pubkey', pubkey) : localStorage.removeItem('pubkey');
if(pubkey && pubkey.length > 0) {
updateStore('publicKey', () => pubkey);
localStorage.setItem('pubkey', pubkey);
checkMembershipStatus();
}
else {
updateStore('publicKey', () => undefined);
localStorage.removeItem('pubkey');
}
updateStore('isKeyLookupDone', () => true);
};

38
src/lib/membership.ts Normal file
View 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;
}
}

View File

@ -124,3 +124,21 @@ export const subscribeTo = (subId: string, cb: (type: NostrEventType, subId: str
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
View File

@ -663,3 +663,15 @@ export type ContactsData = {
tags: 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,
};