feat: safe sync appdata
This commit is contained in:
@ -16,7 +16,7 @@ import { GiftsCache } from "@/Cache";
|
||||
import SnortApi from "@/External/SnortApi";
|
||||
import { bech32ToHex, dedupeById, deleteRefCode, getCountry, sanitizeRelayUrl, unwrap } from "@/Utils";
|
||||
import { Blasters } from "@/Utils/Const";
|
||||
import { LoginSession, LoginSessionType, LoginStore, Newest, SnortAppData, UserPreferences } from "@/Utils/Login/index";
|
||||
import { LoginSession, LoginSessionType, LoginStore, SnortAppData, UserPreferences } from "@/Utils/Login/index";
|
||||
import { entropyToPrivateKey, generateBip39Entropy } from "@/Utils/nip6";
|
||||
import { SubscriptionEvent } from "@/Utils/Subscription";
|
||||
|
||||
@ -56,12 +56,9 @@ export function removeRelay(state: LoginSession, addr: string) {
|
||||
LoginStore.updateSession(state);
|
||||
}
|
||||
|
||||
export function updatePreferences(id: string, p: UserPreferences) {
|
||||
updateAppData(id, d => {
|
||||
return {
|
||||
item: { ...d, preferences: p },
|
||||
timestamp: unixNowMs(),
|
||||
};
|
||||
export async function updatePreferences(id: string, p: UserPreferences, system: SystemInterface) {
|
||||
await updateAppData(id, system, d => {
|
||||
return { ...d, preferences: p };
|
||||
});
|
||||
}
|
||||
|
||||
@ -206,23 +203,19 @@ export function setBookmarked(state: LoginSession, bookmarked: Array<string>, ts
|
||||
LoginStore.updateSession(state);
|
||||
}
|
||||
|
||||
export function setAppData(state: LoginSession, data: SnortAppData, ts: number) {
|
||||
if (state.appData.timestamp >= ts) {
|
||||
return;
|
||||
}
|
||||
state.appData.item = data;
|
||||
state.appData.timestamp = ts;
|
||||
export async function setAppData(state: LoginSession, data: SnortAppData, system: SystemInterface) {
|
||||
const pub = LoginStore.getPublisher(state.id);
|
||||
if (!pub) return;
|
||||
|
||||
await state.appData.updateJson(data, pub.signer, system);
|
||||
LoginStore.updateSession(state);
|
||||
}
|
||||
|
||||
export function updateAppData(id: string, fn: (data: SnortAppData) => Newest<SnortAppData>) {
|
||||
export async function updateAppData(id: string, system: SystemInterface, fn: (data: SnortAppData) => SnortAppData) {
|
||||
const session = LoginStore.get(id);
|
||||
if (session) {
|
||||
const next = fn(session.appData.item);
|
||||
if (next.timestamp > session.appData.timestamp) {
|
||||
session.appData = next;
|
||||
LoginStore.updateSession(session);
|
||||
}
|
||||
const next = fn(session.appData.json);
|
||||
await setAppData(session, next, system);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { HexKey, KeyStorage, RelaySettings, u256 } from "@snort/system";
|
||||
import { HexKey, JsonEventSync, KeyStorage, RelaySettings } from "@snort/system";
|
||||
|
||||
import { DisplayAs } from "@/Components/Feed/DisplayAsSelector";
|
||||
import { UserPreferences } from "@/Utils/Login/index";
|
||||
@ -21,6 +21,7 @@ export const enum LoginSessionType {
|
||||
}
|
||||
|
||||
export interface SnortAppData {
|
||||
id: string;
|
||||
mutedWords: Array<string>;
|
||||
showContentWarningPosts: boolean;
|
||||
preferences: UserPreferences;
|
||||
@ -81,12 +82,12 @@ export interface LoginSession {
|
||||
/**
|
||||
* A list of event ids this user has pinned
|
||||
*/
|
||||
pinned: Newest<Array<u256>>;
|
||||
pinned: Newest<Array<string>>;
|
||||
|
||||
/**
|
||||
* A list of event ids this user has bookmarked
|
||||
*/
|
||||
bookmarked: Newest<Array<u256>>;
|
||||
bookmarked: Newest<Array<string>>;
|
||||
|
||||
/**
|
||||
* A list of pubkeys this user has muted
|
||||
@ -116,7 +117,7 @@ export interface LoginSession {
|
||||
/**
|
||||
* Snort application data
|
||||
*/
|
||||
appData: Newest<SnortAppData>;
|
||||
appData: JsonEventSync<SnortAppData>;
|
||||
|
||||
/**
|
||||
* A list of chats which we have joined (NIP-28/NIP-29)
|
||||
|
@ -1,12 +1,20 @@
|
||||
import * as utils from "@noble/curves/abstract/utils";
|
||||
import * as secp from "@noble/curves/secp256k1";
|
||||
import { ExternalStore, unwrap } from "@snort/shared";
|
||||
import { EventPublisher, HexKey, KeyStorage, NotEncrypted, RelaySettings, socialGraphInstance } from "@snort/system";
|
||||
import {
|
||||
EventPublisher,
|
||||
HexKey,
|
||||
JsonEventSync,
|
||||
KeyStorage,
|
||||
NotEncrypted,
|
||||
RelaySettings,
|
||||
socialGraphInstance,
|
||||
} from "@snort/system";
|
||||
import { v4 as uuid } from "uuid";
|
||||
|
||||
import { createPublisher, LoginSession, LoginSessionType } from "@/Utils/Login/index";
|
||||
import { createPublisher, LoginSession, LoginSessionType, SnortAppData } from "@/Utils/Login/index";
|
||||
|
||||
import { DefaultPreferences, UserPreferences } from "./Preferences";
|
||||
import { DefaultPreferences } from "./Preferences";
|
||||
|
||||
const AccountStoreKey = "sessions";
|
||||
const LoggedOut = {
|
||||
@ -44,14 +52,15 @@ const LoggedOut = {
|
||||
latestNotification: 0,
|
||||
readNotifications: 0,
|
||||
subscriptions: [],
|
||||
appData: {
|
||||
item: {
|
||||
mutedWords: [],
|
||||
appData: new JsonEventSync<SnortAppData>(
|
||||
{
|
||||
id: "",
|
||||
preferences: DefaultPreferences,
|
||||
mutedWords: [],
|
||||
showContentWarningPosts: false,
|
||||
},
|
||||
timestamp: 0,
|
||||
},
|
||||
true,
|
||||
),
|
||||
extraChats: [],
|
||||
stalker: false,
|
||||
} as LoginSession;
|
||||
@ -87,19 +96,14 @@ export class MultiAccountStore extends ExternalStore<LoginSession> {
|
||||
if (v.type === LoginSessionType.PrivateKey && v.readonly) {
|
||||
v.readonly = false;
|
||||
}
|
||||
// fill possibly undefined (migrate up)
|
||||
v.appData ??= {
|
||||
item: {
|
||||
mutedWords: [],
|
||||
showContentWarningPosts: false,
|
||||
preferences: DefaultPreferences,
|
||||
},
|
||||
timestamp: 0,
|
||||
};
|
||||
v.extraChats ??= [];
|
||||
if (v.privateKeyData) {
|
||||
v.privateKeyData = KeyStorage.fromPayload(v.privateKeyData as object);
|
||||
}
|
||||
v.appData = new JsonEventSync<SnortAppData>(v.appData as unknown as SnortAppData, true);
|
||||
v.appData.on("change", () => {
|
||||
this.#save();
|
||||
});
|
||||
}
|
||||
this.#loadIrisKeyIfExists();
|
||||
}
|
||||
@ -163,10 +167,18 @@ export class MultiAccountStore extends ExternalStore<LoginSession> {
|
||||
item: initRelays,
|
||||
timestamp: 1,
|
||||
},
|
||||
preferences: {
|
||||
...DefaultPreferences,
|
||||
...CONFIG.defaultPreferences,
|
||||
},
|
||||
appData: new JsonEventSync<SnortAppData>(
|
||||
{
|
||||
id: "",
|
||||
preferences: {
|
||||
...DefaultPreferences,
|
||||
...CONFIG.defaultPreferences,
|
||||
},
|
||||
mutedWords: [],
|
||||
showContentWarningPosts: false,
|
||||
},
|
||||
true,
|
||||
),
|
||||
remoteSignerRelays,
|
||||
privateKeyData: privateKey,
|
||||
stalker: stalker ?? false,
|
||||
@ -209,10 +221,18 @@ export class MultiAccountStore extends ExternalStore<LoginSession> {
|
||||
item: initRelays,
|
||||
timestamp: 1,
|
||||
},
|
||||
preferences: {
|
||||
...DefaultPreferences,
|
||||
...CONFIG.defaultPreferences,
|
||||
},
|
||||
appData: new JsonEventSync<SnortAppData>(
|
||||
{
|
||||
id: "",
|
||||
preferences: {
|
||||
...DefaultPreferences,
|
||||
...CONFIG.defaultPreferences,
|
||||
},
|
||||
mutedWords: [],
|
||||
showContentWarningPosts: false,
|
||||
},
|
||||
true,
|
||||
),
|
||||
} as LoginSession;
|
||||
|
||||
if ("nostr_os" in window && window?.nostr_os) {
|
||||
@ -271,41 +291,10 @@ export class MultiAccountStore extends ExternalStore<LoginSession> {
|
||||
#migrate() {
|
||||
let didMigrate = false;
|
||||
|
||||
// update session types
|
||||
for (const [, v] of this.#accounts) {
|
||||
if (!v.type) {
|
||||
v.type = v.privateKey ? LoginSessionType.PrivateKey : LoginSessionType.Nip7;
|
||||
didMigrate = true;
|
||||
}
|
||||
}
|
||||
|
||||
// add ids
|
||||
for (const [, v] of this.#accounts) {
|
||||
if ((v.id?.length ?? 0) === 0) {
|
||||
v.id = uuid();
|
||||
didMigrate = true;
|
||||
}
|
||||
}
|
||||
|
||||
// mark readonly
|
||||
for (const [, v] of this.#accounts) {
|
||||
if (v.type === LoginSessionType.PublicKey && !v.readonly) {
|
||||
v.readonly = true;
|
||||
didMigrate = true;
|
||||
}
|
||||
// reset readonly on load
|
||||
if (v.type === LoginSessionType.PrivateKey && v.readonly) {
|
||||
v.readonly = false;
|
||||
didMigrate = true;
|
||||
}
|
||||
}
|
||||
|
||||
// move preferences to appdata
|
||||
for (const [, v] of this.#accounts) {
|
||||
if ("preferences" in v) {
|
||||
v.appData.item.preferences = v.preferences as UserPreferences;
|
||||
delete v["preferences"];
|
||||
for (const [, acc] of this.#accounts) {
|
||||
if ("item" in acc.appData) {
|
||||
didMigrate = true;
|
||||
acc.appData = new JsonEventSync<SnortAppData>(acc.appData.item as SnortAppData, true);
|
||||
}
|
||||
}
|
||||
|
||||
@ -324,10 +313,14 @@ export class MultiAccountStore extends ExternalStore<LoginSession> {
|
||||
if (v.privateKeyData instanceof KeyStorage) {
|
||||
toSave.push({
|
||||
...v,
|
||||
appData: v.appData.json,
|
||||
privateKeyData: v.privateKeyData.toPayload(),
|
||||
});
|
||||
} else {
|
||||
toSave.push({ ...v });
|
||||
toSave.push({
|
||||
...v,
|
||||
appData: v.appData.json,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -65,7 +65,7 @@ export interface UploadProgress {
|
||||
export type UploadStage = "starting" | "hashing" | "uploading" | "done" | undefined;
|
||||
|
||||
export default function useFileUpload(): Uploader {
|
||||
const fileUploader = useLogin(s => s.appData.item.preferences.fileUploader);
|
||||
const fileUploader = useLogin(s => s.appData.json.preferences.fileUploader);
|
||||
const { publisher } = useEventPublisher();
|
||||
const [progress, setProgress] = useState<Array<UploadProgress>>([]);
|
||||
const [stage, setStage] = useState<UploadStage>();
|
||||
|
@ -537,7 +537,7 @@ export function trackEvent(
|
||||
if (
|
||||
!import.meta.env.DEV &&
|
||||
CONFIG.features.analytics &&
|
||||
(LoginStore.snapshot().appData.item.preferences.telemetry ?? true)
|
||||
(LoginStore.snapshot().appData.json.preferences.telemetry ?? true)
|
||||
) {
|
||||
fetch("https://pa.v0l.io/api/event", {
|
||||
method: "POST",
|
||||
|
Reference in New Issue
Block a user