about summary refs log tree commit diff
diff options
context:
space:
mode:
authorEric Bailey <git@esb.lol>2023-12-05 19:59:34 -0600
committerGitHub <noreply@github.com>2023-12-05 19:59:34 -0600
commit3c8036587e28d8fc90e87e4818e3f4e080fbd091 (patch)
tree4ead93f29432f91afd9c7abcac1caa790e303092
parenta915a57b10e9e8a62d769d4de359a916dcf4ceab (diff)
downloadvoidsky-3c8036587e28d8fc90e87e4818e3f4e080fbd091.tar.zst
Improvements to persisted state migration (#2098)
* Fix session email/emailConfirmed types, update usage for safer access

* Handle fallback better, better errors

* Better handling, add test

* Add test for default data

* Remove fallback, not needed, update logs
-rw-r--r--src/state/persisted/__tests__/legacy.test.ts13
-rw-r--r--src/state/persisted/index.ts5
-rw-r--r--src/state/persisted/legacy.ts33
-rw-r--r--src/state/persisted/schema.ts7
-rw-r--r--src/state/session/index.tsx4
-rw-r--r--src/view/com/modals/ChangeEmail.tsx4
-rw-r--r--src/view/com/modals/VerifyEmail.tsx6
-rw-r--r--src/view/screens/Settings.tsx2
-rw-r--r--src/view/shell/desktop/RightNav.tsx4
9 files changed, 48 insertions, 30 deletions
diff --git a/src/state/persisted/__tests__/legacy.test.ts b/src/state/persisted/__tests__/legacy.test.ts
new file mode 100644
index 000000000..7f4b138a1
--- /dev/null
+++ b/src/state/persisted/__tests__/legacy.test.ts
@@ -0,0 +1,13 @@
+import {expect, test} from '@jest/globals'
+
+import {transform} from '#/state/persisted/legacy'
+import {defaults, schema} from '#/state/persisted/schema'
+
+test('defaults', () => {
+  expect(() => schema.parse(defaults)).not.toThrow()
+})
+
+test('transform', () => {
+  const data = transform({})
+  expect(() => schema.parse(data)).not.toThrow()
+})
diff --git a/src/state/persisted/index.ts b/src/state/persisted/index.ts
index 545fdc0e1..67d8b78c6 100644
--- a/src/state/persisted/index.ts
+++ b/src/state/persisted/index.ts
@@ -26,7 +26,10 @@ export async function init() {
   try {
     await migrate() // migrate old store
     const stored = await store.read() // check for new store
-    if (!stored) await store.write(defaults) // opt: init new store
+    if (!stored) {
+      logger.info('persisted state: initializing default storage')
+      await store.write(defaults) // opt: init new store
+    }
     _state = stored || defaults // return new store
     logger.log('persisted state: initialized')
   } catch (e) {
diff --git a/src/state/persisted/legacy.ts b/src/state/persisted/legacy.ts
index 025877529..c45b18322 100644
--- a/src/state/persisted/legacy.ts
+++ b/src/state/persisted/legacy.ts
@@ -1,7 +1,7 @@
 import AsyncStorage from '@react-native-async-storage/async-storage'
 
 import {logger} from '#/logger'
-import {defaults, Schema} from '#/state/persisted/schema'
+import {defaults, Schema, schema} from '#/state/persisted/schema'
 import {write, read} from '#/state/persisted/store'
 
 /**
@@ -66,7 +66,6 @@ type LegacySchema = {
 
 const DEPRECATED_ROOT_STATE_STORAGE_KEY = 'root'
 
-// TODO remove, assume that partial data may be here during our refactor
 export function transform(legacy: Partial<LegacySchema>): Schema {
   return {
     colorMode: legacy.shell?.colorMode || defaults.colorMode,
@@ -116,7 +115,7 @@ export function transform(legacy: Partial<LegacySchema>): Schema {
  * local storage AND old storage exists.
  */
 export async function migrate() {
-  logger.info('persisted state: migrate')
+  logger.info('persisted state: check need to migrate')
 
   try {
     const rawLegacyData = await AsyncStorage.getItem(
@@ -138,6 +137,7 @@ export async function migrate() {
           ),
         })
         logger.info(`persisted state: debug new data`, {
+          hasNewData: Boolean(newData),
           hasExistingLoggedInAccount: Boolean(newData?.session?.currentAccount),
           numberOfExistingAccounts: newData?.session?.accounts?.length,
           existingAccountMatchesLegacy: Boolean(
@@ -145,27 +145,32 @@ export async function migrate() {
               legacy?.session?.data?.did,
           ),
         })
-      } else {
-        logger.info(`persisted state: no legacy to debug, fresh install`)
       }
-    } catch (e) {
-      logger.error(`persisted state: legacy debugging failed`, {error: e})
+    } catch (e: any) {
+      logger.error(e, {message: `persisted state: legacy debugging failed`})
     }
 
     if (!alreadyMigrated && rawLegacyData) {
       logger.info('persisted state: migrating legacy storage')
+
       const legacyData = JSON.parse(rawLegacyData)
       const newData = transform(legacyData)
-      await write(newData)
-      // track successful migrations
-      logger.log('persisted state: migrated legacy storage')
+      const validate = schema.safeParse(newData)
+
+      if (validate.success) {
+        await write(newData)
+        logger.log('persisted state: migrated legacy storage')
+      } else {
+        logger.error('persisted state: legacy data failed validation', {
+          error: validate.error,
+        })
+      }
     } else {
-      // track successful migrations
       logger.log('persisted state: no migration needed')
     }
-  } catch (e) {
-    logger.error('persisted state: error migrating legacy storage', {
-      error: String(e),
+  } catch (e: any) {
+    logger.error(e, {
+      message: 'persisted state: error migrating legacy storage',
     })
   }
 }
diff --git a/src/state/persisted/schema.ts b/src/state/persisted/schema.ts
index 71f9bd545..5ed8e01f3 100644
--- a/src/state/persisted/schema.ts
+++ b/src/state/persisted/schema.ts
@@ -2,17 +2,14 @@ import {z} from 'zod'
 import {deviceLocales} from '#/platform/detection'
 
 // only data needed for rendering account page
-// TODO agent.resumeSession requires the following fields
 const accountSchema = z.object({
   service: z.string(),
   did: z.string(),
   handle: z.string(),
-  email: z.string(),
-  emailConfirmed: z.boolean(),
+  email: z.string().optional(),
+  emailConfirmed: z.boolean().optional(),
   refreshJwt: z.string().optional(), // optional because it can expire
   accessJwt: z.string().optional(), // optional because it can expire
-  // displayName: z.string().optional(),
-  // aviUrl: z.string().optional(),
 })
 export type PersistedAccount = z.infer<typeof accountSchema>
 
diff --git a/src/state/session/index.tsx b/src/state/session/index.tsx
index e6def1fab..37454187a 100644
--- a/src/state/session/index.tsx
+++ b/src/state/session/index.tsx
@@ -245,7 +245,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
         service: agent.service.toString(),
         did: agent.session.did,
         handle: agent.session.handle,
-        email: agent.session.email!, // TODO this is always defined?
+        email: agent.session.email,
         emailConfirmed: agent.session.emailConfirmed || false,
         refreshJwt: agent.session.refreshJwt,
         accessJwt: agent.session.accessJwt,
@@ -342,7 +342,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
         service: agent.service.toString(),
         did: agent.session.did,
         handle: agent.session.handle,
-        email: agent.session.email!, // TODO this is always defined?
+        email: agent.session.email,
         emailConfirmed: agent.session.emailConfirmed || false,
         refreshJwt: agent.session.refreshJwt,
         accessJwt: agent.session.accessJwt,
diff --git a/src/view/com/modals/ChangeEmail.tsx b/src/view/com/modals/ChangeEmail.tsx
index 73ab33dd4..44b102fa0 100644
--- a/src/view/com/modals/ChangeEmail.tsx
+++ b/src/view/com/modals/ChangeEmail.tsx
@@ -118,8 +118,8 @@ export function Component() {
           ) : stage === Stages.ConfirmCode ? (
             <Trans>
               An email has been sent to your previous address,{' '}
-              {currentAccount?.email || ''}. It includes a confirmation code
-              which you can enter below.
+              {currentAccount?.email || '(no email)'}. It includes a
+              confirmation code which you can enter below.
             </Trans>
           ) : (
             <Trans>
diff --git a/src/view/com/modals/VerifyEmail.tsx b/src/view/com/modals/VerifyEmail.tsx
index 4376a3e45..786a814a7 100644
--- a/src/view/com/modals/VerifyEmail.tsx
+++ b/src/view/com/modals/VerifyEmail.tsx
@@ -108,8 +108,8 @@ export function Component({showReminder}: {showReminder?: boolean}) {
             </Trans>
           ) : stage === Stages.ConfirmCode ? (
             <Trans>
-              An email has been sent to {currentAccount?.email || ''}. It
-              includes a confirmation code which you can enter below.
+              An email has been sent to {currentAccount?.email || '(no email)'}.
+              It includes a confirmation code which you can enter below.
             </Trans>
           ) : (
             ''
@@ -125,7 +125,7 @@ export function Component({showReminder}: {showReminder?: boolean}) {
                 size={16}
               />
               <Text type="xl-medium" style={[pal.text, s.flex1, {minWidth: 0}]}>
-                {currentAccount?.email || ''}
+                {currentAccount?.email || '(no email)'}
               </Text>
             </View>
             <Pressable
diff --git a/src/view/screens/Settings.tsx b/src/view/screens/Settings.tsx
index 388a5d954..b7ba32b0d 100644
--- a/src/view/screens/Settings.tsx
+++ b/src/view/screens/Settings.tsx
@@ -299,7 +299,7 @@ export function SettingsScreen({}: Props) {
                 </>
               )}
               <Text type="lg" style={pal.text}>
-                {currentAccount.email}{' '}
+                {currentAccount.email || '(no email)'}{' '}
               </Text>
               <Link onPress={() => openModal({name: 'change-email'})}>
                 <Text type="lg" style={pal.link}>
diff --git a/src/view/shell/desktop/RightNav.tsx b/src/view/shell/desktop/RightNav.tsx
index 9a5186549..c8196c2c4 100644
--- a/src/view/shell/desktop/RightNav.tsx
+++ b/src/view/shell/desktop/RightNav.tsx
@@ -58,8 +58,8 @@ export function DesktopRightNav() {
                 type="md"
                 style={pal.link}
                 href={FEEDBACK_FORM_URL({
-                  email: currentAccount!.email,
-                  handle: currentAccount!.handle,
+                  email: currentAccount?.email,
+                  handle: currentAccount?.handle,
                 })}
                 text={_(msg`Feedback`)}
               />