+ {typeof placeInQueue === 'number' ? (
+ left to go.
+ ) : (
+ You are in line.
+ )}{' '}
+ {estimatedTime ? (
+
+ We estimate {estimatedTime} until your account is ready.
+
+ ) : (
+
+ We will let you know when your account is ready.
+
+ )}
+
+
+
+ {isWeb && gtMobile && (
+
+
+ {checkBtn}
+
+ )}
+
+
+
+
+
+
+ {(!isWeb || !gtMobile) && (
+
+
+ {checkBtn}
+
+
+
+ )}
+
+ )
+}
+
+function msToString(ms: number | undefined): string | undefined {
+ if (ms && ms > 0) {
+ const estimatedTimeMins = Math.ceil(ms / 60e3)
+ if (estimatedTimeMins > 59) {
+ const estimatedTimeHrs = Math.round(estimatedTimeMins / 60)
+ if (estimatedTimeHrs > 6) {
+ // dont even bother
+ return undefined
+ }
+ // hours
+ return `${estimatedTimeHrs} ${pluralize(estimatedTimeHrs, 'hour')}`
+ }
+ // minutes
+ return `${estimatedTimeMins} ${pluralize(estimatedTimeMins, 'minute')}`
+ }
+ return undefined
+}
diff --git a/src/state/persisted/schema.ts b/src/state/persisted/schema.ts
index a6f2ea06a6..870e14aaf2 100644
--- a/src/state/persisted/schema.ts
+++ b/src/state/persisted/schema.ts
@@ -12,6 +12,7 @@ const accountSchema = z.object({
emailConfirmed: z.boolean().optional(),
refreshJwt: z.string().optional(), // optional because it can expire
accessJwt: z.string().optional(), // optional because it can expire
+ deactivated: z.boolean().optional(),
})
export type PersistedAccount = z.infer
diff --git a/src/state/session/index.tsx b/src/state/session/index.tsx
index e49bc2b390..629aff6dc6 100644
--- a/src/state/session/index.tsx
+++ b/src/state/session/index.tsx
@@ -12,6 +12,7 @@ import {emitSessionDropped} from '../events'
import {useLoggedOutViewControls} from '#/state/shell/logged-out'
import {useCloseAllActiveElements} from '#/state/util'
import {track} from '#/lib/analytics/analytics'
+import {hasProp} from '#/lib/type-guards'
let __globalAgent: BskyAgent = PUBLIC_BSKY_AGENT
@@ -125,6 +126,7 @@ function createPersistSessionHandler(
handle: session?.handle || account.handle,
email: session?.email || account.email,
emailConfirmed: session?.emailConfirmed || account.emailConfirmed,
+ deactivated: isSessionDeactivated(session?.accessJwt),
/*
* Tokens are undefined if the session expires, or if creation fails for
@@ -139,6 +141,7 @@ function createPersistSessionHandler(
did: refreshedAccount.did,
handle: refreshedAccount.handle,
service: refreshedAccount.service,
+ deactivated: refreshedAccount.deactivated,
})
if (expired) {
@@ -235,11 +238,14 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
throw new Error(`session: createAccount failed to establish a session`)
}
- /*dont await*/ agent.upsertProfile(_existing => {
- return {
- displayName: handle,
- }
- })
+ const deactivated = isSessionDeactivated(agent.session.accessJwt)
+ if (!deactivated) {
+ /*dont await*/ agent.upsertProfile(_existing => {
+ return {
+ displayName: handle,
+ }
+ })
+ }
const account: SessionAccount = {
service: agent.service.toString(),
@@ -249,6 +255,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
emailConfirmed: false,
refreshJwt: agent.session.refreshJwt,
accessJwt: agent.session.accessJwt,
+ deactivated,
}
agent.setPersistSessionHandler(
@@ -305,6 +312,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
emailConfirmed: agent.session.emailConfirmed || false,
refreshJwt: agent.session.refreshJwt,
accessJwt: agent.session.accessJwt,
+ deactivated: isSessionDeactivated(agent.session.accessJwt),
}
agent.setPersistSessionHandler(
@@ -392,6 +400,8 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
refreshJwt: account.refreshJwt || '',
did: account.did,
handle: account.handle,
+ deactivated:
+ isSessionDeactivated(account.accessJwt) || account.deactivated,
}
if (canReusePrevSession) {
@@ -402,6 +412,13 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
queryClient.clear()
upsertAccount(account)
+ if (prevSession.deactivated) {
+ // don't attempt to resume
+ // use will be taken to the deactivated screen
+ logger.info(`session: reusing session for deactivated account`)
+ return
+ }
+
// Intentionally not awaited to unblock the UI:
resumeSessionWithFreshAccount()
.then(freshAccount => {
@@ -466,6 +483,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
emailConfirmed: agent.session.emailConfirmed || false,
refreshJwt: agent.session.refreshJwt,
accessJwt: agent.session.accessJwt,
+ deactivated: isSessionDeactivated(agent.session.accessJwt),
}
}
},
@@ -687,3 +705,13 @@ export function useRequireAuth() {
[hasSession, setShowLoggedOut, closeAll],
)
}
+
+export function isSessionDeactivated(accessJwt: string | undefined) {
+ if (accessJwt) {
+ const sessData = jwtDecode(accessJwt)
+ return (
+ hasProp(sessData, 'scope') && sessData.scope === 'com.atproto.deactivated'
+ )
+ }
+ return false
+}
diff --git a/src/view/shell/createNativeStackNavigatorWithAuth.tsx b/src/view/shell/createNativeStackNavigatorWithAuth.tsx
index 9fea6e49f1..7e275502b5 100644
--- a/src/view/shell/createNativeStackNavigatorWithAuth.tsx
+++ b/src/view/shell/createNativeStackNavigatorWithAuth.tsx
@@ -35,6 +35,7 @@ import {
} from '#/state/shell/logged-out'
import {useSession} from '#/state/session'
import {isWeb} from 'platform/detection'
+import {Deactivated} from '#/screens/Deactivated'
import {LoggedOut} from '../com/auth/LoggedOut'
import {Onboarding} from '../com/auth/Onboarding'
@@ -92,7 +93,7 @@ function NativeStackNavigator({
)
// --- our custom logic starts here ---
- const {hasSession} = useSession()
+ const {hasSession, currentAccount} = useSession()
const activeRoute = state.routes[state.index]
const activeDescriptor = descriptors[activeRoute.key]
const activeRouteRequiresAuth = activeDescriptor.options.requireAuth ?? false
@@ -103,6 +104,9 @@ function NativeStackNavigator({
if ((!PWI_ENABLED || activeRouteRequiresAuth) && !hasSession) {
return
}
+ if (hasSession && currentAccount?.deactivated) {
+ return
+ }
if (showLoggedOut) {
return setShowLoggedOut(false)} />
}
diff --git a/yarn.lock b/yarn.lock
index 1075743d8c..c5d70ab5d8 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -48,10 +48,10 @@
typed-emitter "^2.1.0"
zod "^3.21.4"
-"@atproto/api@^0.9.1":
- version "0.9.1"
- resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.9.1.tgz#0b28baefa4af32bc4c05715b8641656f332546c6"
- integrity sha512-DHPc/dGgpf8sgPlfR9meIAk7s4YMll0g7HTq/W/LeaaaY0T6d3ZAtrgvjIU1aKCp5WNzTfzrmz0LIHIX46FHHw==
+"@atproto/api@^0.9.5":
+ version "0.9.5"
+ resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.9.5.tgz#630e5d9520bba38d0cd348c8028ddbb73bd074f8"
+ integrity sha512-4vlwTbiWSkCV0DkfNMawiH+26Fv7txPr4x0vwq6KPIBz28UHPK9UyPseLKxi6/Aok74aPr8ySJ4+nfcmwcp08Q==
dependencies:
"@atproto/common-web" "^0.2.3"
"@atproto/lexicon" "^0.3.1"