about summary refs log tree commit diff
path: root/src/state/session
diff options
context:
space:
mode:
authordan <dan.abramov@gmail.com>2024-08-15 20:58:13 +0100
committerGitHub <noreply@github.com>2024-08-15 20:58:13 +0100
commitb6e515c664d51ffe357c3562fd514301805ade8c (patch)
treeac0193c172d6c7ef62ac59d411c2dfc7ae1614c3 /src/state/session
parentf3b57dd45600c0c8197ce45a0f927b57e0799760 (diff)
downloadvoidsky-b6e515c664d51ffe357c3562fd514301805ade8c.tar.zst
Move global "Sign out" out of the current account row (#4941)
* Rename logout to logoutEveryAccount

* Add logoutCurrentAccount()

* Make all "Log out" buttons refer to current account

Each of these usages is completely contextual and refers to a specific account.

* Add Sign out of all accounts to Settings

* Move single account Sign Out below as well

* Prompt on account removal

* Add Other Accounts header to reduce ambiguity

* Spacing fix

---------

Co-authored-by: Paul Frazee <pfrazee@gmail.com>
Diffstat (limited to 'src/state/session')
-rw-r--r--src/state/session/__tests__/session-test.ts103
-rw-r--r--src/state/session/index.tsx38
-rw-r--r--src/state/session/reducer.ts23
-rw-r--r--src/state/session/types.ts12
4 files changed, 159 insertions, 17 deletions
diff --git a/src/state/session/__tests__/session-test.ts b/src/state/session/__tests__/session-test.ts
index cb4c6a35b..3e22c262c 100644
--- a/src/state/session/__tests__/session-test.ts
+++ b/src/state/session/__tests__/session-test.ts
@@ -76,7 +76,7 @@ describe('session', () => {
 
     state = run(state, [
       {
-        type: 'logged-out',
+        type: 'logged-out-every-account',
       },
     ])
     // Should keep the account but clear out the tokens.
@@ -372,7 +372,7 @@ describe('session', () => {
     state = run(state, [
       {
         // Log everyone out.
-        type: 'logged-out',
+        type: 'logged-out-every-account',
       },
     ])
     expect(state.accounts.length).toBe(3)
@@ -466,7 +466,7 @@ describe('session', () => {
 
     state = run(state, [
       {
-        type: 'logged-out',
+        type: 'logged-out-every-account',
       },
     ])
     expect(state.accounts.length).toBe(1)
@@ -674,6 +674,103 @@ describe('session', () => {
     expect(state.currentAgentState.did).toBe(undefined)
   })
 
+  it('can log out of the current account', () => {
+    let state = getInitialState([])
+
+    const agent1 = new BskyAgent({service: 'https://alice.com'})
+    agent1.sessionManager.session = {
+      active: true,
+      did: 'alice-did',
+      handle: 'alice.test',
+      accessJwt: 'alice-access-jwt-1',
+      refreshJwt: 'alice-refresh-jwt-1',
+    }
+    state = run(state, [
+      {
+        type: 'switched-to-account',
+        newAgent: agent1,
+        newAccount: agentToSessionAccountOrThrow(agent1),
+      },
+    ])
+    expect(state.accounts.length).toBe(1)
+    expect(state.accounts[0].accessJwt).toBe('alice-access-jwt-1')
+    expect(state.accounts[0].refreshJwt).toBe('alice-refresh-jwt-1')
+    expect(state.currentAgentState.did).toBe('alice-did')
+
+    const agent2 = new BskyAgent({service: 'https://bob.com'})
+    agent2.sessionManager.session = {
+      active: true,
+      did: 'bob-did',
+      handle: 'bob.test',
+      accessJwt: 'bob-access-jwt-1',
+      refreshJwt: 'bob-refresh-jwt-1',
+    }
+    state = run(state, [
+      {
+        type: 'switched-to-account',
+        newAgent: agent2,
+        newAccount: agentToSessionAccountOrThrow(agent2),
+      },
+    ])
+    expect(state.accounts.length).toBe(2)
+    expect(state.accounts[0].accessJwt).toBe('bob-access-jwt-1')
+    expect(state.accounts[0].refreshJwt).toBe('bob-refresh-jwt-1')
+    expect(state.currentAgentState.did).toBe('bob-did')
+
+    state = run(state, [
+      {
+        type: 'logged-out-current-account',
+      },
+    ])
+    expect(state.accounts.length).toBe(2)
+    expect(state.accounts[0].accessJwt).toBe(undefined)
+    expect(state.accounts[0].refreshJwt).toBe(undefined)
+    expect(state.accounts[1].accessJwt).toBe('alice-access-jwt-1')
+    expect(state.accounts[1].refreshJwt).toBe('alice-refresh-jwt-1')
+    expect(state.currentAgentState.did).toBe(undefined)
+    expect(printState(state)).toMatchInlineSnapshot(`
+      {
+        "accounts": [
+          {
+            "accessJwt": undefined,
+            "active": true,
+            "did": "bob-did",
+            "email": undefined,
+            "emailAuthFactor": false,
+            "emailConfirmed": false,
+            "handle": "bob.test",
+            "pdsUrl": undefined,
+            "refreshJwt": undefined,
+            "service": "https://bob.com/",
+            "signupQueued": false,
+            "status": undefined,
+          },
+          {
+            "accessJwt": "alice-access-jwt-1",
+            "active": true,
+            "did": "alice-did",
+            "email": undefined,
+            "emailAuthFactor": false,
+            "emailConfirmed": false,
+            "handle": "alice.test",
+            "pdsUrl": undefined,
+            "refreshJwt": "alice-refresh-jwt-1",
+            "service": "https://alice.com/",
+            "signupQueued": false,
+            "status": undefined,
+          },
+        ],
+        "currentAgentState": {
+          "agent": {
+            "service": "https://public.api.bsky.app/",
+          },
+          "did": undefined,
+        },
+        "needsPersist": true,
+      }
+    `)
+  })
+
   it('updates stored account with refreshed tokens', () => {
     let state = getInitialState([])
 
diff --git a/src/state/session/index.tsx b/src/state/session/index.tsx
index ba12f4eae..21fe7f75b 100644
--- a/src/state/session/index.tsx
+++ b/src/state/session/index.tsx
@@ -35,7 +35,8 @@ const AgentContext = React.createContext<BskyAgent | null>(null)
 const ApiContext = React.createContext<SessionApiContext>({
   createAccount: async () => {},
   login: async () => {},
-  logout: async () => {},
+  logoutCurrentAccount: async () => {},
+  logoutEveryAccount: async () => {},
   resumeSession: async () => {},
   removeAccount: () => {},
 })
@@ -115,14 +116,31 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
     [onAgentSessionChange, cancelPendingTask],
   )
 
-  const logout = React.useCallback<SessionApiContext['logout']>(
+  const logoutCurrentAccount = React.useCallback<
+    SessionApiContext['logoutEveryAccount']
+  >(
     logContext => {
       addSessionDebugLog({type: 'method:start', method: 'logout'})
       cancelPendingTask()
       dispatch({
-        type: 'logged-out',
+        type: 'logged-out-current-account',
       })
-      logEvent('account:loggedOut', {logContext})
+      logEvent('account:loggedOut', {logContext, scope: 'current'})
+      addSessionDebugLog({type: 'method:end', method: 'logout'})
+    },
+    [cancelPendingTask],
+  )
+
+  const logoutEveryAccount = React.useCallback<
+    SessionApiContext['logoutEveryAccount']
+  >(
+    logContext => {
+      addSessionDebugLog({type: 'method:start', method: 'logout'})
+      cancelPendingTask()
+      dispatch({
+        type: 'logged-out-every-account',
+      })
+      logEvent('account:loggedOut', {logContext, scope: 'every'})
       addSessionDebugLog({type: 'method:end', method: 'logout'})
     },
     [cancelPendingTask],
@@ -230,11 +248,19 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
     () => ({
       createAccount,
       login,
-      logout,
+      logoutCurrentAccount,
+      logoutEveryAccount,
       resumeSession,
       removeAccount,
     }),
-    [createAccount, login, logout, resumeSession, removeAccount],
+    [
+      createAccount,
+      login,
+      logoutCurrentAccount,
+      logoutEveryAccount,
+      resumeSession,
+      removeAccount,
+    ],
   )
 
   // @ts-ignore
diff --git a/src/state/session/reducer.ts b/src/state/session/reducer.ts
index b49198514..22ba47162 100644
--- a/src/state/session/reducer.ts
+++ b/src/state/session/reducer.ts
@@ -42,7 +42,10 @@ export type Action =
       accountDid: string
     }
   | {
-      type: 'logged-out'
+      type: 'logged-out-current-account'
+    }
+  | {
+      type: 'logged-out-every-account'
     }
   | {
       type: 'synced-accounts'
@@ -138,7 +141,23 @@ let reducer = (state: State, action: Action): State => {
         needsPersist: true,
       }
     }
-    case 'logged-out': {
+    case 'logged-out-current-account': {
+      const {currentAgentState} = state
+      return {
+        accounts: state.accounts.map(a =>
+          a.did === currentAgentState.did
+            ? {
+                ...a,
+                refreshJwt: undefined,
+                accessJwt: undefined,
+              }
+            : a,
+        ),
+        currentAgentState: createPublicAgentState(),
+        needsPersist: true,
+      }
+    }
+    case 'logged-out-every-account': {
       return {
         accounts: state.accounts.map(a => ({
           ...a,
diff --git a/src/state/session/types.ts b/src/state/session/types.ts
index d43b57cca..d32259de9 100644
--- a/src/state/session/types.ts
+++ b/src/state/session/types.ts
@@ -29,12 +29,12 @@ export type SessionApiContext = {
     },
     logContext: LogEvents['account:loggedIn']['logContext'],
   ) => Promise<void>
-  /**
-   * A full logout. Clears the `currentAccount` from session, AND removes
-   * access tokens from all accounts, so that returning as any user will
-   * require a full login.
-   */
-  logout: (logContext: LogEvents['account:loggedOut']['logContext']) => void
+  logoutCurrentAccount: (
+    logContext: LogEvents['account:loggedOut']['logContext'],
+  ) => void
+  logoutEveryAccount: (
+    logContext: LogEvents['account:loggedOut']['logContext'],
+  ) => void
   resumeSession: (account: SessionAccount) => Promise<void>
   removeAccount: (account: SessionAccount) => void
 }