diff --git a/android/app/capacitor.build.gradle b/android/app/capacitor.build.gradle index 15c25c4..24d6448 100644 --- a/android/app/capacitor.build.gradle +++ b/android/app/capacitor.build.gradle @@ -12,6 +12,7 @@ dependencies { implementation project(':capacitor-community-safe-area') implementation project(':capacitor-app') implementation project(':capacitor-keyboard') + implementation project(':capacitor-preferences') implementation project(':capacitor-push-notifications') implementation project(':capawesome-capacitor-badge') implementation project(':nostr-signer-capacitor-plugin') diff --git a/android/capacitor.settings.gradle b/android/capacitor.settings.gradle index 1b7eb5e..5b240b7 100644 --- a/android/capacitor.settings.gradle +++ b/android/capacitor.settings.gradle @@ -11,6 +11,9 @@ project(':capacitor-app').projectDir = new File('../node_modules/.pnpm/@capacito include ':capacitor-keyboard' project(':capacitor-keyboard').projectDir = new File('../node_modules/.pnpm/@capacitor+keyboard@7.0.1_@capacitor+core@7.2.0/node_modules/@capacitor/keyboard/android') +include ':capacitor-preferences' +project(':capacitor-preferences').projectDir = new File('../node_modules/.pnpm/@capacitor+preferences@7.0.2_@capacitor+core@7.2.0/node_modules/@capacitor/preferences/android') + include ':capacitor-push-notifications' project(':capacitor-push-notifications').projectDir = new File('../node_modules/.pnpm/@capacitor+push-notifications@7.0.1_@capacitor+core@7.2.0/node_modules/@capacitor/push-notifications/android') diff --git a/ios/App/Podfile b/ios/App/Podfile index c9b2aa7..ca88c9e 100644 --- a/ios/App/Podfile +++ b/ios/App/Podfile @@ -14,6 +14,7 @@ def capacitor_pods pod 'CapacitorCommunitySafeArea', :path => '../../node_modules/.pnpm/@capacitor-community+safe-area@7.0.0-alpha.1_@capacitor+core@7.2.0/node_modules/@capacitor-community/safe-area' pod 'CapacitorApp', :path => '../../node_modules/.pnpm/@capacitor+app@7.0.1_@capacitor+core@7.2.0/node_modules/@capacitor/app' pod 'CapacitorKeyboard', :path => '../../node_modules/.pnpm/@capacitor+keyboard@7.0.1_@capacitor+core@7.2.0/node_modules/@capacitor/keyboard' + pod 'CapacitorPreferences', :path => '../../node_modules/.pnpm/@capacitor+preferences@7.0.2_@capacitor+core@7.2.0/node_modules/@capacitor/preferences' pod 'CapacitorPushNotifications', :path => '../../node_modules/.pnpm/@capacitor+push-notifications@7.0.1_@capacitor+core@7.2.0/node_modules/@capacitor/push-notifications' pod 'CapawesomeCapacitorBadge', :path => '../../node_modules/.pnpm/@capawesome+capacitor-badge@7.0.1_@capacitor+core@7.2.0/node_modules/@capawesome/capacitor-badge' pod 'NostrSignerCapacitorPlugin', :path => '../../node_modules/.pnpm/nostr-signer-capacitor-plugin@0.0.4_@capacitor+core@7.2.0/node_modules/nostr-signer-capacitor-plugin' diff --git a/package.json b/package.json index bb13075..9e69ffc 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "@capacitor/core": "^7.0.1", "@capacitor/ios": "^7.0.0", "@capacitor/keyboard": "^7.0.0", + "@capacitor/preferences": "^7.0.2", "@capacitor/push-notifications": "^7.0.1", "@capawesome/capacitor-badge": "^7.0.1", "@getalby/sdk": "^5.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c3ec8e7..31dab01 100644 Binary files a/pnpm-lock.yaml and b/pnpm-lock.yaml differ diff --git a/src/app/core/commands.ts b/src/app/core/commands.ts index a4eaa43..c42aa56 100644 --- a/src/app/core/commands.ts +++ b/src/app/core/commands.ts @@ -78,6 +78,7 @@ import { userRoomsByUrl, userSettingsValues, } from "@app/core/state" +import {preferencesStorageProvider} from "@src/lib/storage" // Utils @@ -124,6 +125,7 @@ export const logout = async () => { await clearStorage() localStorage.clear() + await preferencesStorageProvider.clear() } // Synchronization diff --git a/src/app/core/state.ts b/src/app/core/state.ts index 0da5b15..5699de9 100644 --- a/src/app/core/state.ts +++ b/src/app/core/state.ts @@ -31,7 +31,6 @@ import { deriveEventsMapped, withGetter, synced, - localStorageProvider, } from "@welshman/store" import { getIdFilters, @@ -97,6 +96,7 @@ import { publishThunk, } from "@welshman/app" import type {Thunk, Relay} from "@welshman/app" +import {preferencesStorageProvider} from "@src/lib/storage" export const fromCsv = (s: string) => (s || "").split(",").filter(identity) @@ -317,7 +317,7 @@ netContext.isEventValid = (event: TrustedEvent, url: string) => export const canDecrypt = synced({ key: "canDecrypt", defaultValue: false, - storage: localStorageProvider, + storage: preferencesStorageProvider, }) export const SETTINGS = "flotilla/settings" diff --git a/src/app/util/notifications.ts b/src/app/util/notifications.ts index 6430c80..f0ab4bd 100644 --- a/src/app/util/notifications.ts +++ b/src/app/util/notifications.ts @@ -1,5 +1,5 @@ import {derived} from "svelte/store" -import {synced, localStorageProvider, throttled} from "@welshman/store" +import {synced, throttled} from "@welshman/store" import {pubkey, relaysByUrl} from "@welshman/app" import {prop, spec, identity, now, groupBy} from "@welshman/lib" import type {TrustedEvent} from "@welshman/util" @@ -13,13 +13,14 @@ import { makeRoomPath, } from "@app/util/routes" import {chats, hasNip29, getUrlsForEvent, userRoomsByUrl, repositoryStore} from "@app/core/state" +import {preferencesStorageProvider} from "@src/lib/storage" // Checked state export const checked = synced>({ key: "checked", defaultValue: {}, - storage: localStorageProvider, + storage: preferencesStorageProvider, }) export const deriveChecked = (key: string) => derived(checked, prop(key)) diff --git a/src/app/util/theme.ts b/src/app/util/theme.ts index 3784172..2651d2a 100644 --- a/src/app/util/theme.ts +++ b/src/app/util/theme.ts @@ -1,7 +1,8 @@ -import {synced, localStorageProvider} from "@welshman/store" +import {preferencesStorageProvider} from "@src/lib/storage" +import {synced} from "@welshman/store" export const theme = synced({ key: "theme", defaultValue: "dark", - storage: localStorageProvider, + storage: preferencesStorageProvider, }) diff --git a/src/lib/storage.ts b/src/lib/storage.ts new file mode 100644 index 0000000..3aeb511 --- /dev/null +++ b/src/lib/storage.ts @@ -0,0 +1,28 @@ +import {type StorageProvider} from "@welshman/store" +import {Preferences} from "@capacitor/preferences" + +export class PreferencesStorageProvider implements StorageProvider { + get = async (key: string): Promise => { + const result = await Preferences.get({key}) + if (!result.value) return undefined + try { + return JSON.parse(result.value) + } catch (e) { + return undefined + } + } + + p = Promise.resolve() + set = async (key: string, value: T): Promise => { + this.p = this.p.then(async () => await Preferences.set({key, value: JSON.stringify(value)})) + await this.p + } + + clear = async (): Promise => { + await Preferences.clear() + this.p = Promise.resolve() + } +} + +// singleton instance of PreferencesStorageProvider +export const preferencesStorageProvider = new PreferencesStorageProvider() diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index d78b5aa..c6ce64a 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -10,18 +10,18 @@ import {goto} from "$app/navigation" import {sync, localStorageProvider} from "@welshman/store" import { - identity, - call, - memoize, - spec, - sleep, - on, - defer, ago, - WEEK, - TaskQueue, assoc, + call, + defer, dissoc, + identity, + memoize, + on, + sleep, + spec, + TaskQueue, + WEEK, } from "@welshman/lib" import type {TrustedEvent, StampedEvent} from "@welshman/util" import { @@ -82,6 +82,7 @@ import * as net from "@welshman/net" import * as app from "@welshman/app" import {nsecDecode} from "@lib/util" + import {preferencesStorageProvider} from "@lib/storage" import AppContainer from "@app/components/AppContainer.svelte" import ModalContainer from "@app/components/ModalContainer.svelte" import {setupTracking} from "@app/util/tracking" @@ -134,6 +135,44 @@ ...notifications, }) + // migrate from localStorage to capacitor Preferences storage if needed + const runMigration = async () => { + const isSome = (item: any) => { + return item !== undefined && item !== null && item !== "" + } + + const localStoragePubKey = await localStorageProvider.get("pubkey") + if (isSome(localStoragePubKey)) { + await preferencesStorageProvider.set("pubkey", localStoragePubKey) + localStorage.removeItem("pubkey") + } + + const localStorageSessions = await localStorageProvider.get("sessions") + if (isSome(localStorageSessions)) { + await preferencesStorageProvider.set("sessions", localStorageSessions) + localStorage.removeItem("sessions") + } + + const localStorageCanDecrypt = await localStorageProvider.get("canDecrypt") + if (isSome(localStorageCanDecrypt)) { + await preferencesStorageProvider.set("canDecrypt", localStorageCanDecrypt) + localStorage.removeItem("canDecrypt") + } + + const localStorageChecked = await localStorageProvider.get("checked") + if (isSome(localStorageChecked)) { + await preferencesStorageProvider.set("checked", localStorageChecked) + localStorage.removeItem("checked") + } + + const localStorageTheme = await localStorageProvider.get("theme") + if (isSome(localStorageTheme)) { + await preferencesStorageProvider.set("theme", localStorageTheme) + localStorage.removeItem("theme") + } + } + await runMigration() + // Listen for navigation messages from service worker navigator.serviceWorker?.addEventListener("message", event => { if (event.data && event.data.type === "NAVIGATE") { @@ -221,17 +260,17 @@ }) // Sync current pubkey - sync({ + await sync({ key: "pubkey", store: pubkey, - storage: localStorageProvider, + storage: preferencesStorageProvider, }) // Sync user sessions - sync({ + await sync({ key: "sessions", store: sessions, - storage: localStorageProvider, + storage: preferencesStorageProvider, }) await initStorage("flotilla", 8, {