feed cache
This commit is contained in:
@ -1,23 +1,35 @@
|
|||||||
import Dexie, { Table } from "dexie";
|
import Dexie, { Table } from "dexie";
|
||||||
|
import { TaggedRawEvent, u256 } from "Nostr";
|
||||||
import { MetadataCache } from "State/Users";
|
import { MetadataCache } from "State/Users";
|
||||||
import { hexToBech32 } from "Util";
|
import { hexToBech32 } from "Util";
|
||||||
|
|
||||||
export const NAME = 'snortDB'
|
export const NAME = 'snortDB'
|
||||||
export const VERSION = 2
|
export const VERSION = 3
|
||||||
|
|
||||||
|
export interface SubCache {
|
||||||
|
id: string,
|
||||||
|
ids: u256[],
|
||||||
|
until?: number,
|
||||||
|
since?: number,
|
||||||
|
}
|
||||||
|
|
||||||
const STORES = {
|
const STORES = {
|
||||||
users: '++pubkey, name, display_name, picture, nip05, npub'
|
users: '++pubkey, name, display_name, picture, nip05, npub',
|
||||||
|
events: '++id, pubkey, created_at',
|
||||||
|
feeds: '++id'
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SnortDB extends Dexie {
|
export class SnortDB extends Dexie {
|
||||||
users!: Table<MetadataCache>;
|
users!: Table<MetadataCache>;
|
||||||
|
events!: Table<TaggedRawEvent>;
|
||||||
|
feeds!: Table<SubCache>;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super(NAME);
|
super(NAME);
|
||||||
this.version(VERSION).stores(STORES).upgrade(tx => {
|
this.version(VERSION).stores(STORES).upgrade(async tx => {
|
||||||
return tx.table("users").toCollection().modify(user => {
|
await tx.table("users").toCollection().modify(user => {
|
||||||
user.npub = hexToBech32("npub", user.pubkey)
|
user.npub = hexToBech32("npub", user.pubkey)
|
||||||
})
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -78,8 +78,8 @@ export default function useEventPublisher() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
nip42Auth: async (challenge: string, relay:string) => {
|
nip42Auth: async (challenge: string, relay: string) => {
|
||||||
if(pubKey) {
|
if (pubKey) {
|
||||||
const ev = NEvent.ForPubKey(pubKey);
|
const ev = NEvent.ForPubKey(pubKey);
|
||||||
ev.Kind = EventKind.Auth;
|
ev.Kind = EventKind.Auth;
|
||||||
ev.Content = "";
|
ev.Content = "";
|
||||||
|
@ -7,7 +7,7 @@ import useSubscription from "Feed/Subscription";
|
|||||||
export default function useFollowersFeed(pubkey: HexKey) {
|
export default function useFollowersFeed(pubkey: HexKey) {
|
||||||
const sub = useMemo(() => {
|
const sub = useMemo(() => {
|
||||||
let x = new Subscriptions();
|
let x = new Subscriptions();
|
||||||
x.Id = "followers";
|
x.Id = `followers:${pubkey.slice(0, 12)}`;
|
||||||
x.Kinds = new Set([EventKind.ContactList]);
|
x.Kinds = new Set([EventKind.ContactList]);
|
||||||
x.PTags = new Set([pubkey]);
|
x.PTags = new Set([pubkey]);
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ import useSubscription, { NoteStore } from "Feed/Subscription";
|
|||||||
export default function useFollowsFeed(pubkey: HexKey) {
|
export default function useFollowsFeed(pubkey: HexKey) {
|
||||||
const sub = useMemo(() => {
|
const sub = useMemo(() => {
|
||||||
let x = new Subscriptions();
|
let x = new Subscriptions();
|
||||||
x.Id = "follows";
|
x.Id = `follows:${pubkey.slice(0, 12)}`;
|
||||||
x.Kinds = new Set([EventKind.ContactList]);
|
x.Kinds = new Set([EventKind.ContactList]);
|
||||||
x.Authors = new Set([pubkey]);
|
x.Authors = new Set([pubkey]);
|
||||||
|
|
||||||
|
@ -77,10 +77,10 @@ export default function useLoginFeed() {
|
|||||||
return dms;
|
return dms;
|
||||||
}, [pubKey]);
|
}, [pubKey]);
|
||||||
|
|
||||||
const metadataFeed = useSubscription(subMetadata, { leaveOpen: true });
|
const metadataFeed = useSubscription(subMetadata, { leaveOpen: true, cache: true });
|
||||||
const notificationFeed = useSubscription(subNotification, { leaveOpen: true });
|
const notificationFeed = useSubscription(subNotification, { leaveOpen: true, cache: true });
|
||||||
const dmsFeed = useSubscription(subDms, { leaveOpen: true });
|
const dmsFeed = useSubscription(subDms, { leaveOpen: true, cache: true });
|
||||||
const mutedFeed = useSubscription(subMuted, { leaveOpen: true });
|
const mutedFeed = useSubscription(subMuted, { leaveOpen: true, cache: true });
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let contactList = metadataFeed.store.notes.filter(a => a.kind === EventKind.ContactList);
|
let contactList = metadataFeed.store.notes.filter(a => a.kind === EventKind.ContactList);
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
import { useLiveQuery } from "dexie-react-hooks";
|
import { useEffect } from "react";
|
||||||
import { useEffect, useMemo } from "react";
|
|
||||||
import { RootState } from "State/Store";
|
|
||||||
import { MetadataCache } from "State/Users";
|
import { MetadataCache } from "State/Users";
|
||||||
import { useKey, useKeys } from "State/Users/Hooks";
|
import { useKey, useKeys } from "State/Users/Hooks";
|
||||||
import { HexKey } from "Nostr";
|
import { HexKey } from "Nostr";
|
||||||
|
@ -3,6 +3,7 @@ import { System } from "Nostr/System";
|
|||||||
import { TaggedRawEvent } from "Nostr";
|
import { TaggedRawEvent } from "Nostr";
|
||||||
import { Subscriptions } from "Nostr/Subscriptions";
|
import { Subscriptions } from "Nostr/Subscriptions";
|
||||||
import { debounce } from "Util";
|
import { debounce } from "Util";
|
||||||
|
import { db } from "Db";
|
||||||
|
|
||||||
export type NoteStore = {
|
export type NoteStore = {
|
||||||
notes: Array<TaggedRawEvent>,
|
notes: Array<TaggedRawEvent>,
|
||||||
@ -10,7 +11,8 @@ export type NoteStore = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type UseSubscriptionOptions = {
|
export type UseSubscriptionOptions = {
|
||||||
leaveOpen: boolean
|
leaveOpen: boolean,
|
||||||
|
cache: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ReducerArg {
|
interface ReducerArg {
|
||||||
@ -77,33 +79,49 @@ export default function useSubscription(sub: Subscriptions | null, options?: Use
|
|||||||
const [state, dispatch] = useReducer(notesReducer, initStore);
|
const [state, dispatch] = useReducer(notesReducer, initStore);
|
||||||
const [debounceOutput, setDebounceOutput] = useState<number>(0);
|
const [debounceOutput, setDebounceOutput] = useState<number>(0);
|
||||||
const [subDebounce, setSubDebounced] = useState<Subscriptions>();
|
const [subDebounce, setSubDebounced] = useState<Subscriptions>();
|
||||||
|
const useCache = useMemo(() => options?.cache === true, [options]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (sub) {
|
if (sub) {
|
||||||
return debounce(DebounceMs, () => {
|
return debounce(DebounceMs, () => {
|
||||||
dispatch({
|
|
||||||
type: "END",
|
|
||||||
end: false
|
|
||||||
});
|
|
||||||
setSubDebounced(sub);
|
setSubDebounced(sub);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [sub, options]);
|
}, [sub, options]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (sub) {
|
if (subDebounce) {
|
||||||
sub.OnEvent = (e) => {
|
dispatch({
|
||||||
|
type: "END",
|
||||||
|
end: false
|
||||||
|
});
|
||||||
|
|
||||||
|
if (useCache) {
|
||||||
|
// preload notes from db
|
||||||
|
PreloadNotes(subDebounce.Id)
|
||||||
|
.then(ev => {
|
||||||
|
dispatch({
|
||||||
|
type: "EVENT",
|
||||||
|
ev: ev
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(console.warn);
|
||||||
|
}
|
||||||
|
subDebounce.OnEvent = (e) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: "EVENT",
|
type: "EVENT",
|
||||||
ev: e
|
ev: e
|
||||||
});
|
});
|
||||||
|
if (useCache) {
|
||||||
|
db.events.put(e);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
sub.OnEnd = (c) => {
|
subDebounce.OnEnd = (c) => {
|
||||||
if (!(options?.leaveOpen ?? false)) {
|
if (!(options?.leaveOpen ?? false)) {
|
||||||
c.RemoveSubscription(sub.Id);
|
c.RemoveSubscription(subDebounce.Id);
|
||||||
if (sub.IsFinished()) {
|
if (subDebounce.IsFinished()) {
|
||||||
System.RemoveSubscription(sub.Id);
|
System.RemoveSubscription(subDebounce.Id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dispatch({
|
dispatch({
|
||||||
@ -112,14 +130,23 @@ export default function useSubscription(sub: Subscriptions | null, options?: Use
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
console.debug("Adding sub: ", sub.ToObject());
|
console.debug("Adding sub: ", subDebounce.ToObject());
|
||||||
System.AddSubscription(sub);
|
System.AddSubscription(subDebounce);
|
||||||
return () => {
|
return () => {
|
||||||
console.debug("Removing sub: ", sub.ToObject());
|
console.debug("Removing sub: ", subDebounce.ToObject());
|
||||||
System.RemoveSubscription(sub.Id);
|
System.RemoveSubscription(subDebounce.Id);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}, [subDebounce]);
|
}, [subDebounce, useCache]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (subDebounce && useCache) {
|
||||||
|
return debounce(500, () => {
|
||||||
|
TrackNotesInFeed(subDebounce.Id, state.notes)
|
||||||
|
.catch(console.warn);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [state, useCache]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return debounce(DebounceMs, () => {
|
return debounce(DebounceMs, () => {
|
||||||
@ -141,3 +168,23 @@ export default function useSubscription(sub: Subscriptions | null, options?: Use
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lookup cached copy of feed
|
||||||
|
*/
|
||||||
|
const PreloadNotes = async (id: string): Promise<TaggedRawEvent[]> => {
|
||||||
|
const feed = await db.feeds.get(id);
|
||||||
|
if (feed) {
|
||||||
|
const events = await db.events.bulkGet(feed.ids);
|
||||||
|
return events.filter(a => a !== undefined).map(a => a!);
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const TrackNotesInFeed = async (id: string, notes: TaggedRawEvent[]) => {
|
||||||
|
const existing = await db.feeds.get(id);
|
||||||
|
const ids = Array.from(new Set([...(existing?.ids || []), ...notes.map(a => a.id)]));
|
||||||
|
const since = notes.reduce((acc, v) => acc > v.created_at ? v.created_at : acc, +Infinity);
|
||||||
|
const until = notes.reduce((acc, v) => acc < v.created_at ? v.created_at : acc, -Infinity);
|
||||||
|
await db.feeds.put({ id, ids, since, until });
|
||||||
|
}
|
@ -38,7 +38,7 @@ export default function useThreadFeed(id: u256) {
|
|||||||
return thisSub;
|
return thisSub;
|
||||||
}, [trackingEvents, pref, id]);
|
}, [trackingEvents, pref, id]);
|
||||||
|
|
||||||
const main = useSubscription(sub, { leaveOpen: true });
|
const main = useSubscription(sub, { leaveOpen: true, cache: true });
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (main.store) {
|
if (main.store) {
|
||||||
|
@ -14,6 +14,7 @@ export interface TimelineFeedOptions {
|
|||||||
|
|
||||||
export interface TimelineSubject {
|
export interface TimelineSubject {
|
||||||
type: "pubkey" | "hashtag" | "global" | "ptag" | "keyword",
|
type: "pubkey" | "hashtag" | "global" | "ptag" | "keyword",
|
||||||
|
discriminator: string,
|
||||||
items: string[]
|
items: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,7 +33,7 @@ export default function useTimelineFeed(subject: TimelineSubject, options: Timel
|
|||||||
}
|
}
|
||||||
|
|
||||||
let sub = new Subscriptions();
|
let sub = new Subscriptions();
|
||||||
sub.Id = `timeline:${subject.type}`;
|
sub.Id = `timeline:${subject.type}:${subject.discriminator}`;
|
||||||
sub.Kinds = new Set([EventKind.TextNote, EventKind.Repost]);
|
sub.Kinds = new Set([EventKind.TextNote, EventKind.Repost]);
|
||||||
switch (subject.type) {
|
switch (subject.type) {
|
||||||
case "pubkey": {
|
case "pubkey": {
|
||||||
@ -54,7 +55,7 @@ export default function useTimelineFeed(subject: TimelineSubject, options: Timel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return sub;
|
return sub;
|
||||||
}, [subject.type, subject.items]);
|
}, [subject.type, subject.items, subject.discriminator]);
|
||||||
|
|
||||||
const sub = useMemo(() => {
|
const sub = useMemo(() => {
|
||||||
let sub = createSub();
|
let sub = createSub();
|
||||||
@ -86,7 +87,7 @@ export default function useTimelineFeed(subject: TimelineSubject, options: Timel
|
|||||||
return sub;
|
return sub;
|
||||||
}, [until, since, options.method, pref, createSub]);
|
}, [until, since, options.method, pref, createSub]);
|
||||||
|
|
||||||
const main = useSubscription(sub, { leaveOpen: true });
|
const main = useSubscription(sub, { leaveOpen: true, cache: true });
|
||||||
|
|
||||||
const subRealtime = useMemo(() => {
|
const subRealtime = useMemo(() => {
|
||||||
let subLatest = createSub();
|
let subLatest = createSub();
|
||||||
@ -98,7 +99,7 @@ export default function useTimelineFeed(subject: TimelineSubject, options: Timel
|
|||||||
return subLatest;
|
return subLatest;
|
||||||
}, [pref, createSub]);
|
}, [pref, createSub]);
|
||||||
|
|
||||||
const latest = useSubscription(subRealtime, { leaveOpen: true });
|
const latest = useSubscription(subRealtime, { leaveOpen: true, cache: false });
|
||||||
|
|
||||||
const subNext = useMemo(() => {
|
const subNext = useMemo(() => {
|
||||||
let sub: Subscriptions | undefined;
|
let sub: Subscriptions | undefined;
|
||||||
@ -111,7 +112,7 @@ export default function useTimelineFeed(subject: TimelineSubject, options: Timel
|
|||||||
return sub ?? null;
|
return sub ?? null;
|
||||||
}, [trackingEvents, pref, subject.type]);
|
}, [trackingEvents, pref, subject.type]);
|
||||||
|
|
||||||
const others = useSubscription(subNext, { leaveOpen: true });
|
const others = useSubscription(subNext, { leaveOpen: true, cache: true });
|
||||||
|
|
||||||
const subParents = useMemo(() => {
|
const subParents = useMemo(() => {
|
||||||
if (trackingParentEvents.length > 0) {
|
if (trackingParentEvents.length > 0) {
|
||||||
|
@ -8,7 +8,7 @@ const HashTagsPage = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h2>#{tag}</h2>
|
<h2>#{tag}</h2>
|
||||||
<Timeline key={tag} subject={{ type: "hashtag", items: [tag] }} postsOnly={false} method={"TIME_RANGE"} />
|
<Timeline key={tag} subject={{ type: "hashtag", items: [tag], discriminator: tag }} postsOnly={false} method={"TIME_RANGE"} />
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ export default function NotificationsPage() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{pubkey ?
|
{pubkey ?
|
||||||
<Timeline subject={{ type: "ptag", items: [pubkey!] }} postsOnly={false} method={"TIME_RANGE"} />
|
<Timeline subject={{ type: "ptag", items: [pubkey!], discriminator: pubkey!.slice(0, 12) }} postsOnly={false} method={"TIME_RANGE"} />
|
||||||
: null}
|
: null}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
@ -105,7 +105,7 @@ export default function ProfilePage() {
|
|||||||
function tabContent() {
|
function tabContent() {
|
||||||
switch (tab) {
|
switch (tab) {
|
||||||
case ProfileTab.Notes:
|
case ProfileTab.Notes:
|
||||||
return <Timeline key={id} subject={{ type: "pubkey", items: [id] }} postsOnly={false} method={"LIMIT_UNTIL"} ignoreModeration={true} />;
|
return <Timeline key={id} subject={{ type: "pubkey", items: [id], discriminator: id.slice(0, 12) }} postsOnly={false} method={"LIMIT_UNTIL"} ignoreModeration={true} />;
|
||||||
case ProfileTab.Follows: {
|
case ProfileTab.Follows: {
|
||||||
if (isMe) {
|
if (isMe) {
|
||||||
return (
|
return (
|
||||||
@ -195,7 +195,7 @@ export default function ProfilePage() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="profile flex">
|
<div className="profile flex">
|
||||||
{user?.banner && <ProxyImg alt="banner" className="banner" src={user.banner} size={w}/>}
|
{user?.banner && <ProxyImg alt="banner" className="banner" src={user.banner} size={w} />}
|
||||||
<div className="profile-wrapper flex">
|
<div className="profile-wrapper flex">
|
||||||
{avatar()}
|
{avatar()}
|
||||||
{userDetails()}
|
{userDetails()}
|
||||||
|
@ -29,7 +29,7 @@ export default function RootPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const isGlobal = loggedOut || tab === RootTab.Global;
|
const isGlobal = loggedOut || tab === RootTab.Global;
|
||||||
const timelineSubect: TimelineSubject = isGlobal ? { type: "global", items: [] } : { type: "pubkey", items: follows };
|
const timelineSubect: TimelineSubject = isGlobal ? { type: "global", items: [], discriminator: "all" } : { type: "pubkey", items: follows, discriminator: "follows" };
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{pubKey ? <>
|
{pubKey ? <>
|
||||||
|
@ -43,7 +43,7 @@ const SearchPage = () => {
|
|||||||
<div className="flex mb10">
|
<div className="flex mb10">
|
||||||
<input type="text" className="f-grow mr10" placeholder="Search.." value={search} onChange={e => setSearch(e.target.value)} />
|
<input type="text" className="f-grow mr10" placeholder="Search.." value={search} onChange={e => setSearch(e.target.value)} />
|
||||||
</div>
|
</div>
|
||||||
{keyword && <Timeline key={keyword} subject={{ type: "keyword", items: [keyword] }} postsOnly={false} method={"LIMIT_UNTIL"} />}
|
{keyword && <Timeline key={keyword} subject={{ type: "keyword", items: [keyword], discriminator: keyword }} postsOnly={false} method={"LIMIT_UNTIL"} />}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,8 @@ import { UsersDb, MetadataCache, setUsers } from "State/Users";
|
|||||||
import store from "State/Store";
|
import store from "State/Store";
|
||||||
|
|
||||||
class IndexedDb implements UsersDb {
|
class IndexedDb implements UsersDb {
|
||||||
|
ready: boolean = false;
|
||||||
|
|
||||||
isAvailable() {
|
isAvailable() {
|
||||||
if ("indexedDB" in window) {
|
if ("indexedDB" in window) {
|
||||||
return new Promise<boolean>((resolve) => {
|
return new Promise<boolean>((resolve) => {
|
||||||
@ -88,21 +90,21 @@ class ReduxUsersDb implements UsersDb {
|
|||||||
async add(user: MetadataCache) {
|
async add(user: MetadataCache) {
|
||||||
const state = store.getState()
|
const state = store.getState()
|
||||||
const { users } = state.users
|
const { users } = state.users
|
||||||
store.dispatch(setUsers({...users, [user.pubkey]: user }))
|
store.dispatch(setUsers({ ...users, [user.pubkey]: user }))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async put(user: MetadataCache) {
|
async put(user: MetadataCache) {
|
||||||
const state = store.getState()
|
const state = store.getState()
|
||||||
const { users } = state.users
|
const { users } = state.users
|
||||||
store.dispatch(setUsers({...users, [user.pubkey]: user }))
|
store.dispatch(setUsers({ ...users, [user.pubkey]: user }))
|
||||||
}
|
}
|
||||||
|
|
||||||
async bulkAdd(newUserProfiles: MetadataCache[]) {
|
async bulkAdd(newUserProfiles: MetadataCache[]) {
|
||||||
const state = store.getState()
|
const state = store.getState()
|
||||||
const { users } = state.users
|
const { users } = state.users
|
||||||
const newUsers = newUserProfiles.reduce(groupByPubkey, {})
|
const newUsers = newUserProfiles.reduce(groupByPubkey, {})
|
||||||
store.dispatch(setUsers({...users, ...newUsers }))
|
store.dispatch(setUsers({ ...users, ...newUsers }))
|
||||||
}
|
}
|
||||||
|
|
||||||
async bulkGet(keys: HexKey[]) {
|
async bulkGet(keys: HexKey[]) {
|
||||||
@ -118,8 +120,8 @@ class ReduxUsersDb implements UsersDb {
|
|||||||
const state = store.getState()
|
const state = store.getState()
|
||||||
const { users } = state.users
|
const { users } = state.users
|
||||||
const current = users[key]
|
const current = users[key]
|
||||||
const updated = {...current, ...fields }
|
const updated = { ...current, ...fields }
|
||||||
store.dispatch(setUsers({...users, [key]: updated }))
|
store.dispatch(setUsers({ ...users, [key]: updated }))
|
||||||
}
|
}
|
||||||
|
|
||||||
async bulkPut(newUsers: MetadataCache[]) {
|
async bulkPut(newUsers: MetadataCache[]) {
|
||||||
@ -138,6 +140,7 @@ let db: UsersDb = inMemoryDb
|
|||||||
indexedDb.isAvailable().then((available) => {
|
indexedDb.isAvailable().then((available) => {
|
||||||
if (available) {
|
if (available) {
|
||||||
console.debug('Using Indexed DB')
|
console.debug('Using Indexed DB')
|
||||||
|
indexedDb.ready = true;
|
||||||
db = indexedDb;
|
db = indexedDb;
|
||||||
} else {
|
} else {
|
||||||
console.debug('Using in-memory DB')
|
console.debug('Using in-memory DB')
|
||||||
|
Reference in New Issue
Block a user