Merge main into the Web PR (#230)
* Update to RN 71.1.0 (#100) * Update to RN 71 * Adds missing lint plugin * Add missing native changes * Bump @atproto/api@0.0.7 (#112) * Image not loading on swipe (#114) * Adds prefetching to images * Adds image prefetch * bugfix for images not showing on swipe * Fixes prefetch bug * Update src/view/com/util/PostEmbeds.tsx --------- Co-authored-by: Paul Frazee <pfrazee@gmail.com> * Fixes to session management (#117) * Update session-management to solve incorrectly dropped sessions * Reset the nav on account switch * Reset the feed on me.load() * Update tests to reflect new account-switching behavior * Increase max image resolutions and sizes (#118) * Slightly increase the hitslop for post controls * Fix character counter color in dark mode * Update login to use new session.create api, which enables email login (close #93) (#119) * Replaces the alert with dropdown for profile image and banner (#123) * replaces the alert with dropdown for profile image and banner * lint * Fix to ordering of images in the embed grid (#121) * Add explicit link-embed controls to the composer (#120) * Add explicit link-embed controls * Update the target rez/size of link embed thumbs * Remove the alert before publishing without a link card * [Draft] Fixes image failing on reupload issue (#128) * Fixes image failing on reupload issue * Use tmp folder instead of documents * lint * Image performance improvements (#126) * Switch out most images for FastImage * Add image loading placeholders * Fix tests * Collection of fixes to list rendering (#127) * Fix bug that caused endless spinners in profile feeds * Bundle fetches of suggested actors into one update * Fixes to suggested follow rendering * Fix missing replacement of flex:1 to height:100 * Fixes to navigation swipes (#129) * Nav swipe: increase the distance traveled in response to gesture movement. This causes swipes to feel faster and more responsive. * Fix: fully clamp the swipe against the edge * Improve the performance of swipes by skipping the interaction manager * Adds dark mode to the edit screen (#130) * Adds dark mode to edit screen * lint * lint * lint * Reduce render cost of post controls and improve perceived responsiveness (#132) * Move post control animations into conditional render and increase perceived responsiveness * Remove log * Adds dark mode to the dropdown (#131) * Adds dark mode to the bottom sheet * Make background button lighter (like before) * lint * Fix bug in lightbox rendering (#133) * Fix layout in onboarding to not overflow the footer * Configure feed FlatList (removeClippedSubviews=true) to improve scroll performance (#136) * Disable like/repost animations to see if theyre causing #135 (#137) * Composer: mention tagging now works in middle of text (close #105) (#139) * Implement account deletion (#141) * Fix photo & camera permission management (#140) * Check photo & camera perms and alert the user if not available (close #64) - Adds perms checks with a prompt to update settings if needed - Moves initial access of photos in the composer so that the initial prompt occurs at an intuitive time. * Add react-native-permissions test mock * Fix issue causing multiple access requests * Use longer var names * Update podfile.lock * Lint fix * Move photo perm request in composer to the gallery btn instead of when the carousel is opened * Adds more tracking all around the app (#142) * Adds more tracking all around the app * more events * lint * using better analytics naming * missed file * more fixes * Calculate image aspect ratio on load (#146) * Calculate image aspect ratio on load * Move aspect ratio bounds to constants * Adds detox testing and instructions (#147) * Adds detox testing and instructions * lint * lint * Error cleanup (close #79) (#148) * Avoid surfacing errors to the user when it's not critical * Remove now-unused GetAssertionsView * Apply cleanError() consistently * Give a better error message for Upstream Failures (http status 502) * Hide errors in notifications because they're not useful * More e2e tests (create account) (#150) * Adds respots under the 'post' tab under profile (#158) * Adds dark mode to delete account screen (#159) * 87 dark mode edit profile (#162) * Adds dark mode to delete account screen * Adds one more missed darkmode * more fixes * Remove fallback gradient on external links without thumbs (#164) * Remove fallback gradient on external links without thumbs * Remove fallback gradient on external links without thumbs in the composer preview * Fix refresh behavior around a series of models (repost, graph, vote) (#163) * Fix refresh behavior around a series of models (repost, graph, vote) * Fix cursor behavior in reposted-by view * Fixes issue where retrying on image upload fails (#166) * Fixes issue where retrying on image upload fails * Lint, longer test time * Longer waitfor time in tests * even longer timeout * longer timeout * missed file * Update src/view/com/composer/ComposePost.tsx Co-authored-by: Paul Frazee <pfrazee@gmail.com> * Update src/view/com/composer/ComposePost.tsx Co-authored-by: Paul Frazee <pfrazee@gmail.com> --------- Co-authored-by: Paul Frazee <pfrazee@gmail.com> * 154 cached image profile (#167) * Fixes issue where retrying on image upload fails * Lint, longer test time * Longer waitfor time in tests * even longer timeout * longer timeout * missed file * Fixes image cache error on second try for profile screen * lint * lint * lint * Refactor session management to use a new "Agent" API (#165) * Add the atp-agent implementation (temporarily in this repo) * Rewrite all session & API management to use the new atp-agent * Update tests for the atp-agent refactor * Refactor management of session-related state. Includes: - More careful management of when state is cleared or fetched - Debug logging to help trace future issues - Clearer APIs overall * Bubble session-expiration events to the user and display a toast to explain * Switch to the new @atproto/api@0.1.0 * Minor aesthetic cleanup in SessionModel * Wire up ReportAccount and ReportPost (#168) * Fixes embeds for youtube channels (#169) * Bump app ios version to 1.1 (needed after app store submission) * Fix potential issues with promise guards when an error occurs (#170) * Refactor models to use bundleAsync and lock regions (#171) * Fix to an edge case with feed re-ordering for threads (#172) * 151 fix youtube channel embed (#173) * Fixes embeds for youtube channels * Tests for youtube extract meta * lint * Add 'doesnt use non-exempt encryption' to ios config * Rework the search UI and add (#174) * Add search tab and move icon to footer * Remove subtitles from view header * Remove unused code * Clean up UI of search screen * Search: give better user feedback to UI state and add a cancel button * Add WhoToFollow section to search * Add a temporary SuggestedPosts solution using the patented 'bsky team algo' * Trigger reload of suggested content in search on open * Wait five min between reloading discovery content * Reduce weight of solid search icon in footer * Fix lint * Fix tests * 151 feat youtube embed iframe (#176) * youtube embed iframe temp commit * Fixes styling and code cleanup * lint * Now clicking between the pause and settings button doesn't trigger the parent * use modest branding (less yt logos) * Stop playing the video once there's a navigation event * Make sure the iframe is unmounted on any navigation event * fixes tests * lint * Add scroll-to-top for all screens (#177) * Adds hardcoded suggested list (#178) * Adds hardcoded suggested list * Update suggested-actors-view to support page sizes smaller than the hardcoded list --------- Co-authored-by: Paul Frazee <pfrazee@gmail.com> * more robust centering of the play button (#181) Co-authored-by: Aryan Goharzad <arrygoo@gmail.com> * Bundle of UI modifications (#175) * Adjust visual balance of SuggestedPosts and WhoToFollow * Fix bug in the discovery load trigger * Adjust search header aesthetic and have it scroll away * More visual balance tweaks on the search page * Even more visual balance tweaks on the search page * Hide the footer on scroll in search * Ditch the composer prompt buttons in the home feed * Center the view header title * Hide header on scroll on the home feed * Fix e2e tests * Fix home feed positioning (closes #189) (#195) * Fix home feed positioning for floating header * Fix positioning of errors in home feed * Fix lint * Don't show new-content notification for reposts (close #179) (#197) * Show the splash screen during session resumption (close #186) (#199) * Fix to suggested follows: chunk the hardcoded fetches to 25 at a time (close #196) (#198) * UI updates to the floating action button (#201) * Update FAB to use a plus icon and not drop shadow * Update FAB positioning to be more consistent in different shell modes * Animate the FAB's repositioning * Remove the 'loading' placeholder from images as it degraded feed perf (#202) * Remove the 'loading' placeholder from images as it degraded feed perf * Remove references * Fix RN bug that causes home feed not to load more; also fix home feed load view. (#208) RN has a bug where rendering a flatlist with an empty array appears to break its virtual list windowing behaviors. See https://stackoverflow.com/a/67873596 * Only give the loading spinner on the home feed during PTR (#207) (cherry picked from commit b7a5da12fdfacef74873b5cf6d75f20d259bde0e) * Implement our own lifecycle tracking to ensure it never fires while the app is backgrounded (close #193) (#211) * Push notification fixes (#210) * Fix to when screen analytics events are firing * Fix: dont trigger update state when backgrounded * Small fix to notifee API usage * Fix: properly load notification info for push card * Add feedback link to main menu (close #191) (#212) * Add "follows you" information and sync follow state between views (#215) * Bump @atproto/api@0.1.2 and update API usage * Add 'follows you' pill to profile header (close #110) * Add 'follows you' to followers and follows (close #103) * Update reposted-by and liked-by views to use the same components as followers and following * Create a local follows cache MyFollowsModel to keep views in sync (close #205) * Add incremental hydration to the MyFollows model * Fix tests * Update deps * Fix lint * Fix to paginated fetches * Fix reference * Fix potential state-desync issue * Fixes to notifications (#216) * Improve push-notification for follows * Refresh notifications on screen open (close #214) * Avoid showing loader more than needed in post threads * Refactor notification polling to handle view-state more effectively * Delete a bunch of tests taht werent adding value * Remove the accounts integration test; we'll use the e2e test instead * Load latest in notifications when the screen is open rather than full refresh * Randomize hard-coded suggested follows (#226) * Ensure follows are loaded before filtering hardcoded suggestions * Randomize hard-coded suggested profiles (close #219) * Sanitizes posts on publish and render (#217) * Sanatizes posts on publish and render * lint * lint and added sanitize to thread view as well * adjusts indices based on replaced text * Woops, fixes a bug * bugfix + cleanup * comment * lint * move sanitize text to later in the flow * undo changes to compose post * Add RichText library building upon the sanitizePost library method * Add lodash.clonedeep dep * Switch to RichText processing on record load & render * Fix lint --------- Co-authored-by: Paul Frazee <pfrazee@gmail.com> * A group of notifications fixes (#227) * Fix: don't group together notifications that can't visually be grouped (close #221) * Mark all notifications read on PTR * Small optimization: useCallback and useMemo in posts feed * Add loading spinner to footer of notifications (close #222) * Fix to scrolling to posts within a thread (#228) * Fix: render the entire thread at start so that scrollToIndex works always (close #270) * Visual fixes to thread 'load more' * A few small perf improvements to thread rendering * Fix lint * 1.2 * Remove unused logger lib * Remove state-mock * Type fixes * Reorganize the folder structure for lib and switch to typescript path aliases * Move build-flags into lib * Move to the state path alias * Add view path alias * Fix lint * iOS build fixes * Wrap analytics in native/web splitter and re-enable in all view code * Add web version of react-native-webview * Add web split for version number * Fix BlurView import for web * Add web split for fastimage * Create web split for permissions lib * Fix for web high priority images --------- Co-authored-by: Aryan Goharzad <arrygoo@gmail.com>
This commit is contained in:
parent
7916b26aad
commit
f28334739b
242 changed files with 8400 additions and 7454 deletions
|
@ -1,25 +1,22 @@
|
|||
import {makeAutoObservable, runInAction} from 'mobx'
|
||||
import {makeAutoObservable} from 'mobx'
|
||||
import {
|
||||
sessionClient as AtpApi,
|
||||
Session,
|
||||
SessionServiceClient,
|
||||
AtpAgent,
|
||||
AtpSessionEvent,
|
||||
AtpSessionData,
|
||||
ComAtprotoServerGetAccountsConfig as GetAccountsConfig,
|
||||
} from '@atproto/api'
|
||||
import {isObj, hasProp} from '../lib/type-guards'
|
||||
import normalizeUrl from 'normalize-url'
|
||||
import {isObj, hasProp} from 'lib/type-guards'
|
||||
import {z} from 'zod'
|
||||
import {RootStoreModel} from './root-store'
|
||||
import {isNetworkError} from '../../lib/errors'
|
||||
|
||||
export type ServiceDescription = GetAccountsConfig.OutputSchema
|
||||
|
||||
export const sessionData = z.object({
|
||||
export const activeSession = z.object({
|
||||
service: z.string(),
|
||||
refreshJwt: z.string(),
|
||||
accessJwt: z.string(),
|
||||
handle: z.string(),
|
||||
did: z.string(),
|
||||
})
|
||||
export type SessionData = z.infer<typeof sessionData>
|
||||
export type ActiveSession = z.infer<typeof activeSession>
|
||||
|
||||
export const accountData = z.object({
|
||||
service: z.string(),
|
||||
|
@ -32,18 +29,24 @@ export const accountData = z.object({
|
|||
})
|
||||
export type AccountData = z.infer<typeof accountData>
|
||||
|
||||
interface AdditionalAccountData {
|
||||
displayName?: string
|
||||
aviUrl?: string
|
||||
}
|
||||
|
||||
export class SessionModel {
|
||||
/**
|
||||
* Current session data
|
||||
* Currently-active session
|
||||
*/
|
||||
data: SessionData | null = null
|
||||
data: ActiveSession | null = null
|
||||
/**
|
||||
* A listing of the currently & previous sessions, used for account switching
|
||||
* A listing of the currently & previous sessions
|
||||
*/
|
||||
accounts: AccountData[] = []
|
||||
online = false
|
||||
attemptingConnect = false
|
||||
private _connectPromise: Promise<boolean> | undefined
|
||||
/**
|
||||
* Flag to indicate if we're doing our initial-load session resumption
|
||||
*/
|
||||
isResumingSession = false
|
||||
|
||||
constructor(public rootStore: RootStoreModel) {
|
||||
makeAutoObservable(this, {
|
||||
|
@ -53,8 +56,22 @@ export class SessionModel {
|
|||
})
|
||||
}
|
||||
|
||||
get currentSession() {
|
||||
if (!this.data) {
|
||||
return undefined
|
||||
}
|
||||
const {did, service} = this.data
|
||||
return this.accounts.find(
|
||||
account =>
|
||||
normalizeUrl(account.service) === normalizeUrl(service) &&
|
||||
account.did === did &&
|
||||
!!account.accessJwt &&
|
||||
!!account.refreshJwt,
|
||||
)
|
||||
}
|
||||
|
||||
get hasSession() {
|
||||
return this.data !== null
|
||||
return !!this.currentSession && !!this.rootStore.agent.session
|
||||
}
|
||||
|
||||
get hasAccounts() {
|
||||
|
@ -75,8 +92,8 @@ export class SessionModel {
|
|||
hydrate(v: unknown) {
|
||||
this.accounts = []
|
||||
if (isObj(v)) {
|
||||
if (hasProp(v, 'data') && sessionData.safeParse(v.data)) {
|
||||
this.data = v.data as SessionData
|
||||
if (hasProp(v, 'data') && activeSession.safeParse(v.data)) {
|
||||
this.data = v.data as ActiveSession
|
||||
}
|
||||
if (hasProp(v, 'accounts') && Array.isArray(v.accounts)) {
|
||||
for (const account of v.accounts) {
|
||||
|
@ -90,92 +107,96 @@ export class SessionModel {
|
|||
|
||||
clear() {
|
||||
this.data = null
|
||||
this.setOnline(false)
|
||||
}
|
||||
|
||||
setState(data: SessionData) {
|
||||
this.data = data
|
||||
}
|
||||
|
||||
setOnline(online: boolean, attemptingConnect?: boolean) {
|
||||
this.online = online
|
||||
if (typeof attemptingConnect === 'boolean') {
|
||||
this.attemptingConnect = attemptingConnect
|
||||
}
|
||||
}
|
||||
|
||||
updateAuthTokens(session: Session) {
|
||||
if (this.data) {
|
||||
this.setState({
|
||||
...this.data,
|
||||
accessJwt: session.accessJwt,
|
||||
refreshJwt: session.refreshJwt,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the XRPC API, must be called before connecting to a service
|
||||
* Attempts to resume the previous session loaded from storage
|
||||
*/
|
||||
private configureApi(): boolean {
|
||||
if (!this.data) {
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
const serviceUri = new URL(this.data.service)
|
||||
this.rootStore.api.xrpc.uri = serviceUri
|
||||
} catch (e: any) {
|
||||
this.rootStore.log.error(
|
||||
`Invalid service URL: ${this.data.service}. Resetting session.`,
|
||||
e,
|
||||
async attemptSessionResumption() {
|
||||
const sess = this.currentSession
|
||||
if (sess) {
|
||||
this.rootStore.log.debug(
|
||||
'SessionModel:attemptSessionResumption found stored session',
|
||||
)
|
||||
this.isResumingSession = true
|
||||
try {
|
||||
return await this.resumeSession(sess)
|
||||
} finally {
|
||||
this.isResumingSession = false
|
||||
}
|
||||
} else {
|
||||
this.rootStore.log.debug(
|
||||
'SessionModel:attemptSessionResumption has no session to resume',
|
||||
)
|
||||
this.clear()
|
||||
return false
|
||||
}
|
||||
|
||||
this.rootStore.api.sessionManager.set({
|
||||
refreshJwt: this.data.refreshJwt,
|
||||
accessJwt: this.data.accessJwt,
|
||||
})
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Upserts the current session into the accounts
|
||||
* Sets the active session
|
||||
*/
|
||||
private addSessionToAccounts() {
|
||||
if (!this.data) {
|
||||
return
|
||||
setActiveSession(agent: AtpAgent, did: string) {
|
||||
this.rootStore.log.debug('SessionModel:setActiveSession')
|
||||
this.data = {
|
||||
service: agent.service.toString(),
|
||||
did,
|
||||
}
|
||||
this.rootStore.handleSessionChange(agent)
|
||||
}
|
||||
|
||||
/**
|
||||
* Upserts a session into the accounts
|
||||
*/
|
||||
private persistSession(
|
||||
service: string,
|
||||
did: string,
|
||||
event: AtpSessionEvent,
|
||||
session?: AtpSessionData,
|
||||
addedInfo?: AdditionalAccountData,
|
||||
) {
|
||||
this.rootStore.log.debug('SessionModel:persistSession', {
|
||||
service,
|
||||
did,
|
||||
event,
|
||||
hasSession: !!session,
|
||||
})
|
||||
|
||||
// upsert the account in our listing
|
||||
const existingAccount = this.accounts.find(
|
||||
acc => acc.service === this.data?.service && acc.did === this.data.did,
|
||||
account => account.service === service && account.did === did,
|
||||
)
|
||||
const newAccount = {
|
||||
service: this.data.service,
|
||||
refreshJwt: this.data.refreshJwt,
|
||||
accessJwt: this.data.accessJwt,
|
||||
handle: this.data.handle,
|
||||
did: this.data.did,
|
||||
displayName: this.rootStore.me.displayName,
|
||||
aviUrl: this.rootStore.me.avatar,
|
||||
service,
|
||||
did,
|
||||
refreshJwt: session?.refreshJwt,
|
||||
accessJwt: session?.accessJwt,
|
||||
handle: session?.handle || existingAccount?.handle || '',
|
||||
displayName: addedInfo
|
||||
? addedInfo.displayName
|
||||
: existingAccount?.displayName || '',
|
||||
aviUrl: addedInfo ? addedInfo.aviUrl : existingAccount?.aviUrl || '',
|
||||
}
|
||||
if (!existingAccount) {
|
||||
this.accounts.push(newAccount)
|
||||
} else {
|
||||
this.accounts = this.accounts
|
||||
.filter(
|
||||
acc =>
|
||||
!(acc.service === this.data?.service && acc.did === this.data.did),
|
||||
)
|
||||
.concat([newAccount])
|
||||
this.accounts = [
|
||||
newAccount,
|
||||
...this.accounts.filter(
|
||||
account => !(account.service === service && account.did === did),
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
// if the session expired, fire an event to let the user know
|
||||
if (event === 'expired') {
|
||||
this.rootStore.handleSessionDrop()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears any session tokens from the accounts; used on logout.
|
||||
*/
|
||||
private clearSessionTokensFromAccounts() {
|
||||
private clearSessionTokens() {
|
||||
this.rootStore.log.debug('SessionModel:clearSessionTokens')
|
||||
this.accounts = this.accounts.map(acct => ({
|
||||
service: acct.service,
|
||||
handle: acct.handle,
|
||||
|
@ -186,144 +207,106 @@ export class SessionModel {
|
|||
}
|
||||
|
||||
/**
|
||||
* Fetches the current session from the service, if possible.
|
||||
* Requires an existing session (.data) to be populated with access tokens.
|
||||
* Fetches additional information about an account on load.
|
||||
*/
|
||||
async connect(): Promise<boolean> {
|
||||
if (this._connectPromise) {
|
||||
return this._connectPromise
|
||||
}
|
||||
this._connectPromise = this._connect()
|
||||
const res = await this._connectPromise
|
||||
this._connectPromise = undefined
|
||||
return res
|
||||
}
|
||||
|
||||
private async _connect(): Promise<boolean> {
|
||||
this.attemptingConnect = true
|
||||
if (!this.configureApi()) {
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
const sess = await this.rootStore.api.com.atproto.session.get()
|
||||
if (sess.success && this.data && this.data.did === sess.data.did) {
|
||||
this.setOnline(true, false)
|
||||
if (this.rootStore.me.did !== sess.data.did) {
|
||||
this.rootStore.me.clear()
|
||||
}
|
||||
this.rootStore.me
|
||||
.load()
|
||||
.catch(e => {
|
||||
this.rootStore.log.error(
|
||||
'Failed to fetch local user information',
|
||||
e,
|
||||
)
|
||||
})
|
||||
.then(() => {
|
||||
this.addSessionToAccounts()
|
||||
})
|
||||
return true // success
|
||||
}
|
||||
} catch (e: any) {
|
||||
if (isNetworkError(e)) {
|
||||
this.setOnline(false, false) // connection issue
|
||||
return false
|
||||
} else {
|
||||
this.clear() // invalid session cached
|
||||
private async loadAccountInfo(agent: AtpAgent, did: string) {
|
||||
const res = await agent.api.app.bsky.actor
|
||||
.getProfile({actor: did})
|
||||
.catch(_e => undefined)
|
||||
if (res) {
|
||||
return {
|
||||
dispayName: res.data.displayName,
|
||||
aviUrl: res.data.avatar,
|
||||
}
|
||||
}
|
||||
|
||||
this.setOnline(false, false)
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to fetch the accounts config settings from an account.
|
||||
*/
|
||||
async describeService(service: string): Promise<ServiceDescription> {
|
||||
const api = AtpApi.service(service) as SessionServiceClient
|
||||
const res = await api.com.atproto.server.getAccountsConfig({})
|
||||
const agent = new AtpAgent({service})
|
||||
const res = await agent.api.com.atproto.server.getAccountsConfig({})
|
||||
return res.data
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to resume a session that we still have access tokens for.
|
||||
*/
|
||||
async resumeSession(account: AccountData): Promise<boolean> {
|
||||
this.rootStore.log.debug('SessionModel:resumeSession')
|
||||
if (!(account.accessJwt && account.refreshJwt && account.service)) {
|
||||
this.rootStore.log.debug(
|
||||
'SessionModel:resumeSession aborted due to lack of access tokens',
|
||||
)
|
||||
return false
|
||||
}
|
||||
|
||||
const agent = new AtpAgent({
|
||||
service: account.service,
|
||||
persistSession: (evt: AtpSessionEvent, sess?: AtpSessionData) => {
|
||||
this.persistSession(account.service, account.did, evt, sess)
|
||||
},
|
||||
})
|
||||
|
||||
try {
|
||||
await agent.resumeSession({
|
||||
accessJwt: account.accessJwt,
|
||||
refreshJwt: account.refreshJwt,
|
||||
did: account.did,
|
||||
handle: account.handle,
|
||||
})
|
||||
const addedInfo = await this.loadAccountInfo(agent, account.did)
|
||||
this.persistSession(
|
||||
account.service,
|
||||
account.did,
|
||||
'create',
|
||||
agent.session,
|
||||
addedInfo,
|
||||
)
|
||||
this.rootStore.log.debug('SessionModel:resumeSession succeeded')
|
||||
} catch (e: any) {
|
||||
this.rootStore.log.debug('SessionModel:resumeSession failed', {
|
||||
error: e.toString(),
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
this.setActiveSession(agent, account.did)
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new session.
|
||||
*/
|
||||
async login({
|
||||
service,
|
||||
handle,
|
||||
identifier,
|
||||
password,
|
||||
}: {
|
||||
service: string
|
||||
handle: string
|
||||
identifier: string
|
||||
password: string
|
||||
}) {
|
||||
const api = AtpApi.service(service) as SessionServiceClient
|
||||
const res = await api.com.atproto.session.create({handle, password})
|
||||
if (res.data.accessJwt && res.data.refreshJwt) {
|
||||
this.setState({
|
||||
service: service,
|
||||
accessJwt: res.data.accessJwt,
|
||||
refreshJwt: res.data.refreshJwt,
|
||||
handle: res.data.handle,
|
||||
did: res.data.did,
|
||||
})
|
||||
this.configureApi()
|
||||
this.setOnline(true, false)
|
||||
this.rootStore.me
|
||||
.load()
|
||||
.catch(e => {
|
||||
this.rootStore.log.error('Failed to fetch local user information', e)
|
||||
})
|
||||
.then(() => {
|
||||
this.addSessionToAccounts()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to resume a session that we still have access tokens for.
|
||||
*/
|
||||
async resumeSession(account: AccountData): Promise<boolean> {
|
||||
if (!(account.accessJwt && account.refreshJwt && account.service)) {
|
||||
return false
|
||||
this.rootStore.log.debug('SessionModel:login')
|
||||
const agent = new AtpAgent({service})
|
||||
await agent.login({identifier, password})
|
||||
if (!agent.session) {
|
||||
throw new Error('Failed to establish session')
|
||||
}
|
||||
|
||||
// test that the session is good
|
||||
const api = AtpApi.service(account.service)
|
||||
api.sessionManager.set({
|
||||
refreshJwt: account.refreshJwt,
|
||||
accessJwt: account.accessJwt,
|
||||
})
|
||||
try {
|
||||
const sess = await api.com.atproto.session.get()
|
||||
if (
|
||||
!sess.success ||
|
||||
sess.data.did !== account.did ||
|
||||
!api.sessionManager.session
|
||||
) {
|
||||
return false
|
||||
}
|
||||
const did = agent.session.did
|
||||
const addedInfo = await this.loadAccountInfo(agent, did)
|
||||
|
||||
// copy over the access tokens, as they may have refreshed during the .get() above
|
||||
runInAction(() => {
|
||||
account.refreshJwt = api.sessionManager.session?.refreshJwt
|
||||
account.accessJwt = api.sessionManager.session?.accessJwt
|
||||
})
|
||||
} catch (_e) {
|
||||
return false
|
||||
}
|
||||
this.persistSession(service, did, 'create', agent.session, addedInfo)
|
||||
agent.setPersistSessionHandler(
|
||||
(evt: AtpSessionEvent, sess?: AtpSessionData) => {
|
||||
this.persistSession(service, did, evt, sess)
|
||||
},
|
||||
)
|
||||
|
||||
// session is good, connect
|
||||
this.setState({
|
||||
service: account.service,
|
||||
accessJwt: account.accessJwt,
|
||||
refreshJwt: account.refreshJwt,
|
||||
handle: account.handle,
|
||||
did: account.did,
|
||||
})
|
||||
return this.connect()
|
||||
this.setActiveSession(agent, did)
|
||||
this.rootStore.log.debug('SessionModel:login succeeded')
|
||||
}
|
||||
|
||||
async createAccount({
|
||||
|
@ -339,38 +322,41 @@ export class SessionModel {
|
|||
handle: string
|
||||
inviteCode?: string
|
||||
}) {
|
||||
const api = AtpApi.service(service) as SessionServiceClient
|
||||
const res = await api.com.atproto.account.create({
|
||||
this.rootStore.log.debug('SessionModel:createAccount')
|
||||
const agent = new AtpAgent({service})
|
||||
await agent.createAccount({
|
||||
handle,
|
||||
password,
|
||||
email,
|
||||
inviteCode,
|
||||
})
|
||||
if (res.data.accessJwt && res.data.refreshJwt) {
|
||||
this.setState({
|
||||
service: service,
|
||||
accessJwt: res.data.accessJwt,
|
||||
refreshJwt: res.data.refreshJwt,
|
||||
handle: res.data.handle,
|
||||
did: res.data.did,
|
||||
})
|
||||
this.rootStore.onboard.start()
|
||||
this.configureApi()
|
||||
this.rootStore.me
|
||||
.load()
|
||||
.catch(e => {
|
||||
this.rootStore.log.error('Failed to fetch local user information', e)
|
||||
})
|
||||
.then(() => {
|
||||
this.addSessionToAccounts()
|
||||
})
|
||||
if (!agent.session) {
|
||||
throw new Error('Failed to establish session')
|
||||
}
|
||||
|
||||
const did = agent.session.did
|
||||
const addedInfo = await this.loadAccountInfo(agent, did)
|
||||
|
||||
this.persistSession(service, did, 'create', agent.session, addedInfo)
|
||||
agent.setPersistSessionHandler(
|
||||
(evt: AtpSessionEvent, sess?: AtpSessionData) => {
|
||||
this.persistSession(service, did, evt, sess)
|
||||
},
|
||||
)
|
||||
|
||||
this.setActiveSession(agent, did)
|
||||
this.rootStore.onboard.start()
|
||||
this.rootStore.log.debug('SessionModel:createAccount succeeded')
|
||||
}
|
||||
|
||||
/**
|
||||
* Close all sessions across all accounts.
|
||||
*/
|
||||
async logout() {
|
||||
this.rootStore.log.debug('SessionModel:logout')
|
||||
// TODO
|
||||
// need to evaluate why deleting the session has caused errors at times
|
||||
// -prf
|
||||
/*if (this.hasSession) {
|
||||
this.rootStore.api.com.atproto.session.delete().catch((e: any) => {
|
||||
this.rootStore.log.warn(
|
||||
|
@ -379,7 +365,7 @@ export class SessionModel {
|
|||
)
|
||||
})
|
||||
}*/
|
||||
this.clearSessionTokensFromAccounts()
|
||||
this.rootStore.clearAll()
|
||||
this.clearSessionTokens()
|
||||
this.rootStore.clearAllSessionState()
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue