about summary refs log tree commit diff
diff options
context:
space:
mode:
authorHailey <me@haileyok.com>2024-08-12 14:00:15 -0700
committerGitHub <noreply@github.com>2024-08-12 14:00:15 -0700
commit7df2327424e948e54b9731e5ab651e889f38a772 (patch)
treecd6513394de29696124374d1a72bd4cd78cbc1a7
parentae883e2df7bc53baca215fba527fe113e71cb5c2 (diff)
downloadvoidsky-7df2327424e948e54b9731e5ab651e889f38a772.tar.zst
Upgrade API, implement XRPC rework (#4857)
Co-authored-by: Matthieu Sieben <matthieu.sieben@gmail.com>
-rw-r--r--index.js9
-rw-r--r--index.web.js5
-rw-r--r--jest/test-pds.ts4
-rw-r--r--package.json4
-rw-r--r--patches/@atproto+lexicon+0.4.0.patch28
-rw-r--r--src/lib/api/api-polyfill.ts85
-rw-r--r--src/lib/api/api-polyfill.web.ts3
-rw-r--r--src/lib/api/feed/custom.ts25
-rw-r--r--src/lib/api/index.ts39
-rw-r--r--src/lib/api/upload-blob.ts82
-rw-r--r--src/lib/api/upload-blob.web.ts26
-rw-r--r--src/lib/media/manip.ts8
-rw-r--r--src/screens/SignupQueued.tsx2
-rw-r--r--src/state/queries/preferences/index.ts4
-rw-r--r--src/state/session/__tests__/session-test.ts72
-rw-r--r--src/state/session/agent.ts44
-rw-r--r--src/state/session/index.tsx24
-rw-r--r--src/state/session/logging.ts2
-rw-r--r--yarn.lock435
19 files changed, 542 insertions, 359 deletions
diff --git a/index.js b/index.js
index 7630d0538..2f13ce1ea 100644
--- a/index.js
+++ b/index.js
@@ -1,14 +1,11 @@
 import 'react-native-gesture-handler' // must be first
-import {LogBox} from 'react-native'
-
 import '#/platform/polyfills'
-import {IS_TEST} from '#/env'
+
+import {LogBox} from 'react-native'
 import {registerRootComponent} from 'expo'
-import {doPolyfill} from '#/lib/api/api-polyfill'
 
 import App from '#/App'
-
-doPolyfill()
+import {IS_TEST} from '#/env'
 
 if (IS_TEST) {
   LogBox.ignoreAllLogs() // suppress all logs in tests
diff --git a/index.web.js b/index.web.js
index 962373451..be75bc772 100644
--- a/index.web.js
+++ b/index.web.js
@@ -1,9 +1,8 @@
 import '#/platform/markBundleStartTime'
-
 import '#/platform/polyfills'
+
 import {registerRootComponent} from 'expo'
-import {doPolyfill} from '#/lib/api/api-polyfill'
+
 import App from '#/App'
 
-doPolyfill()
 registerRootComponent(App)
diff --git a/jest/test-pds.ts b/jest/test-pds.ts
index 2fe623ca9..bfcc970c2 100644
--- a/jest/test-pds.ts
+++ b/jest/test-pds.ts
@@ -156,7 +156,7 @@ class Mocker {
   }
 
   async createUser(name: string) {
-    const agent = new BskyAgent({service: this.agent.service})
+    const agent = new BskyAgent({service: this.service})
 
     const inviteRes = await agent.api.com.atproto.server.createInviteCode(
       {useCount: 1},
@@ -332,7 +332,7 @@ class Mocker {
   }
 
   async createInvite(forAccount: string) {
-    const agent = new BskyAgent({service: this.agent.service})
+    const agent = new BskyAgent({service: this.service})
     await agent.api.com.atproto.server.createInviteCode(
       {useCount: 1, forAccount},
       {
diff --git a/package.json b/package.json
index 7c6e13afb..a4523d988 100644
--- a/package.json
+++ b/package.json
@@ -52,7 +52,7 @@
     "open-analyzer": "EXPO_PUBLIC_OPEN_ANALYZER=1 yarn build-web"
   },
   "dependencies": {
-    "@atproto/api": "0.12.29",
+    "@atproto/api": "0.13.0",
     "@bam.tech/react-native-image-resizer": "^3.0.4",
     "@braintree/sanitize-url": "^6.0.2",
     "@discord/bottom-sheet": "bluesky-social/react-native-bottom-sheet",
@@ -208,7 +208,7 @@
     "zod": "^3.20.2"
   },
   "devDependencies": {
-    "@atproto/dev-env": "^0.3.5",
+    "@atproto/dev-env": "^0.3.39",
     "@babel/core": "^7.23.2",
     "@babel/preset-env": "^7.20.0",
     "@babel/runtime": "^7.20.0",
diff --git a/patches/@atproto+lexicon+0.4.0.patch b/patches/@atproto+lexicon+0.4.0.patch
deleted file mode 100644
index 4643db32a..000000000
--- a/patches/@atproto+lexicon+0.4.0.patch
+++ /dev/null
@@ -1,28 +0,0 @@
-diff --git a/node_modules/@atproto/lexicon/dist/validators/complex.js b/node_modules/@atproto/lexicon/dist/validators/complex.js
-index 32d7798..9d688b7 100644
---- a/node_modules/@atproto/lexicon/dist/validators/complex.js
-+++ b/node_modules/@atproto/lexicon/dist/validators/complex.js
-@@ -113,7 +113,22 @@ function object(lexicons, path, def, value) {
-             if (value[key] === null && nullableProps.has(key)) {
-                 continue;
-             }
--            const propDef = def.properties[key];
-+            const propDef = def.properties[key]
-+            if (typeof value[key] === 'undefined' && !requiredProps.has(key)) {
-+              // Fast path for non-required undefined props.
-+              if (
-+                propDef.type === 'integer' ||
-+                propDef.type === 'boolean' ||
-+                propDef.type === 'string'
-+              ) {
-+                if (typeof propDef.default === 'undefined') {
-+                  continue
-+                }
-+              } else {
-+                // Other types have no defaults.
-+                continue
-+              }
-+            }
-             const propPath = `${path}/${key}`;
-             const validated = (0, util_1.validateOneOf)(lexicons, propPath, propDef, value[key]);
-             const propValue = validated.success ? validated.value : value[key];
diff --git a/src/lib/api/api-polyfill.ts b/src/lib/api/api-polyfill.ts
deleted file mode 100644
index e3aec7631..000000000
--- a/src/lib/api/api-polyfill.ts
+++ /dev/null
@@ -1,85 +0,0 @@
-import RNFS from 'react-native-fs'
-import {BskyAgent, jsonToLex, stringifyLex} from '@atproto/api'
-
-const GET_TIMEOUT = 15e3 // 15s
-const POST_TIMEOUT = 60e3 // 60s
-
-export function doPolyfill() {
-  BskyAgent.configure({fetch: fetchHandler})
-}
-
-interface FetchHandlerResponse {
-  status: number
-  headers: Record<string, string>
-  body: any
-}
-
-async function fetchHandler(
-  reqUri: string,
-  reqMethod: string,
-  reqHeaders: Record<string, string>,
-  reqBody: any,
-): Promise<FetchHandlerResponse> {
-  const reqMimeType = reqHeaders['Content-Type'] || reqHeaders['content-type']
-  if (reqMimeType && reqMimeType.startsWith('application/json')) {
-    reqBody = stringifyLex(reqBody)
-  } else if (
-    typeof reqBody === 'string' &&
-    (reqBody.startsWith('/') || reqBody.startsWith('file:'))
-  ) {
-    if (reqBody.endsWith('.jpeg') || reqBody.endsWith('.jpg')) {
-      // HACK
-      // React native has a bug that inflates the size of jpegs on upload
-      // we get around that by renaming the file ext to .bin
-      // see https://github.com/facebook/react-native/issues/27099
-      // -prf
-      const newPath = reqBody.replace(/\.jpe?g$/, '.bin')
-      await RNFS.moveFile(reqBody, newPath)
-      reqBody = newPath
-    }
-    // NOTE
-    // React native treats bodies with {uri: string} as file uploads to pull from cache
-    // -prf
-    reqBody = {uri: reqBody}
-  }
-
-  const controller = new AbortController()
-  const to = setTimeout(
-    () => controller.abort(),
-    reqMethod === 'post' ? POST_TIMEOUT : GET_TIMEOUT,
-  )
-
-  const res = await fetch(reqUri, {
-    method: reqMethod,
-    headers: reqHeaders,
-    body: reqBody,
-    signal: controller.signal,
-  })
-
-  const resStatus = res.status
-  const resHeaders: Record<string, string> = {}
-  res.headers.forEach((value: string, key: string) => {
-    resHeaders[key] = value
-  })
-  const resMimeType = resHeaders['Content-Type'] || resHeaders['content-type']
-  let resBody
-  if (resMimeType) {
-    if (resMimeType.startsWith('application/json')) {
-      resBody = jsonToLex(await res.json())
-    } else if (resMimeType.startsWith('text/')) {
-      resBody = await res.text()
-    } else if (resMimeType === 'application/vnd.ipld.car') {
-      resBody = await res.arrayBuffer()
-    } else {
-      throw new Error('Non-supported mime type')
-    }
-  }
-
-  clearTimeout(to)
-
-  return {
-    status: resStatus,
-    headers: resHeaders,
-    body: resBody,
-  }
-}
diff --git a/src/lib/api/api-polyfill.web.ts b/src/lib/api/api-polyfill.web.ts
deleted file mode 100644
index 1ad22b3d0..000000000
--- a/src/lib/api/api-polyfill.web.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export function doPolyfill() {
-  // no polyfill is needed on web
-}
diff --git a/src/lib/api/feed/custom.ts b/src/lib/api/feed/custom.ts
index eb54dd29c..6db96a8d6 100644
--- a/src/lib/api/feed/custom.ts
+++ b/src/lib/api/feed/custom.ts
@@ -1,7 +1,6 @@
 import {
   AppBskyFeedDefs,
   AppBskyFeedGetFeed as GetCustomFeed,
-  AtpAgent,
   BskyAgent,
 } from '@atproto/api'
 
@@ -51,7 +50,7 @@ export class CustomFeedAPI implements FeedAPI {
     const agent = this.agent
     const isBlueskyOwned = isBlueskyOwnedFeed(this.params.feed)
 
-    const res = agent.session
+    const res = agent.did
       ? await this.agent.app.bsky.feed.getFeed(
           {
             ...this.params,
@@ -106,34 +105,32 @@ async function loggedOutFetch({
   let contentLangs = getContentLanguages().join(',')
 
   // manually construct fetch call so we can add the `lang` cache-busting param
-  let res = await AtpAgent.fetch!(
+  let res = await fetch(
     `https://api.bsky.app/xrpc/app.bsky.feed.getFeed?feed=${feed}${
       cursor ? `&cursor=${cursor}` : ''
     }&limit=${limit}&lang=${contentLangs}`,
-    'GET',
-    {'Accept-Language': contentLangs},
-    undefined,
+    {method: 'GET', headers: {'Accept-Language': contentLangs}},
   )
-  if (res.body?.feed?.length) {
+  let data = res.ok ? await res.json() : null
+  if (data?.feed?.length) {
     return {
       success: true,
-      data: res.body,
+      data,
     }
   }
 
   // no data, try again with language headers removed
-  res = await AtpAgent.fetch!(
+  res = await fetch(
     `https://api.bsky.app/xrpc/app.bsky.feed.getFeed?feed=${feed}${
       cursor ? `&cursor=${cursor}` : ''
     }&limit=${limit}`,
-    'GET',
-    {'Accept-Language': ''},
-    undefined,
+    {method: 'GET', headers: {'Accept-Language': ''}},
   )
-  if (res.body?.feed?.length) {
+  data = res.ok ? await res.json() : null
+  if (data?.feed?.length) {
     return {
       success: true,
-      data: res.body,
+      data,
     }
   }
 
diff --git a/src/lib/api/index.ts b/src/lib/api/index.ts
index 12e30bf6c..658ed78de 100644
--- a/src/lib/api/index.ts
+++ b/src/lib/api/index.ts
@@ -6,7 +6,6 @@ import {
   AppBskyFeedThreadgate,
   BskyAgent,
   ComAtprotoLabelDefs,
-  ComAtprotoRepoUploadBlob,
   RichText,
 } from '@atproto/api'
 import {AtUri} from '@atproto/api'
@@ -15,10 +14,13 @@ import {logger} from '#/logger'
 import {ThreadgateSetting} from '#/state/queries/threadgate'
 import {isNetworkError} from 'lib/strings/errors'
 import {shortenLinks, stripInvalidMentions} from 'lib/strings/rich-text-manip'
-import {isNative, isWeb} from 'platform/detection'
+import {isNative} from 'platform/detection'
 import {ImageModel} from 'state/models/media/image'
 import {LinkMeta} from '../link-meta/link-meta'
 import {safeDeleteAsync} from '../media/manip'
+import {uploadBlob} from './upload-blob'
+
+export {uploadBlob}
 
 export interface ExternalEmbedDraft {
   uri: string
@@ -28,25 +30,6 @@ export interface ExternalEmbedDraft {
   localThumb?: ImageModel
 }
 
-export async function uploadBlob(
-  agent: BskyAgent,
-  blob: string,
-  encoding: string,
-): Promise<ComAtprotoRepoUploadBlob.Response> {
-  if (isWeb) {
-    // `blob` should be a data uri
-    return agent.uploadBlob(convertDataURIToUint8Array(blob), {
-      encoding,
-    })
-  } else {
-    // `blob` should be a path to a file in the local FS
-    return agent.uploadBlob(
-      blob, // this will be special-cased by the fetch monkeypatch in /src/state/lib/api.ts
-      {encoding},
-    )
-  }
-}
-
 interface PostOpts {
   rawText: string
   replyTo?: string
@@ -301,7 +284,7 @@ export async function createThreadgate(
 
   const postUrip = new AtUri(postUri)
   await agent.api.com.atproto.repo.putRecord({
-    repo: agent.session!.did,
+    repo: agent.accountDid,
     collection: 'app.bsky.feed.threadgate',
     rkey: postUrip.rkey,
     record: {
@@ -312,15 +295,3 @@ export async function createThreadgate(
     },
   })
 }
-
-// helpers
-// =
-
-function convertDataURIToUint8Array(uri: string): Uint8Array {
-  var raw = window.atob(uri.substring(uri.indexOf(';base64,') + 8))
-  var binary = new Uint8Array(new ArrayBuffer(raw.length))
-  for (let i = 0; i < raw.length; i++) {
-    binary[i] = raw.charCodeAt(i)
-  }
-  return binary
-}
diff --git a/src/lib/api/upload-blob.ts b/src/lib/api/upload-blob.ts
new file mode 100644
index 000000000..0814d5185
--- /dev/null
+++ b/src/lib/api/upload-blob.ts
@@ -0,0 +1,82 @@
+import RNFS from 'react-native-fs'
+import {BskyAgent, ComAtprotoRepoUploadBlob} from '@atproto/api'
+
+/**
+ * @param encoding Allows overriding the blob's type
+ */
+export async function uploadBlob(
+  agent: BskyAgent,
+  input: string | Blob,
+  encoding?: string,
+): Promise<ComAtprotoRepoUploadBlob.Response> {
+  if (typeof input === 'string' && input.startsWith('file:')) {
+    const blob = await asBlob(input)
+    return agent.uploadBlob(blob, {encoding})
+  }
+
+  if (typeof input === 'string' && input.startsWith('/')) {
+    const blob = await asBlob(`file://${input}`)
+    return agent.uploadBlob(blob, {encoding})
+  }
+
+  if (typeof input === 'string' && input.startsWith('data:')) {
+    const blob = await fetch(input).then(r => r.blob())
+    return agent.uploadBlob(blob, {encoding})
+  }
+
+  if (input instanceof Blob) {
+    return agent.uploadBlob(input, {encoding})
+  }
+
+  throw new TypeError(`Invalid uploadBlob input: ${typeof input}`)
+}
+
+async function asBlob(uri: string): Promise<Blob> {
+  return withSafeFile(uri, async safeUri => {
+    // Note
+    // Android does not support `fetch()` on `file://` URIs. for this reason, we
+    // use XMLHttpRequest instead of simply calling:
+
+    // return fetch(safeUri.replace('file:///', 'file:/')).then(r => r.blob())
+
+    return await new Promise((resolve, reject) => {
+      const xhr = new XMLHttpRequest()
+      xhr.onload = () => resolve(xhr.response)
+      xhr.onerror = () => reject(new Error('Failed to load blob'))
+      xhr.responseType = 'blob'
+      xhr.open('GET', safeUri, true)
+      xhr.send(null)
+    })
+  })
+}
+
+// HACK
+// React native has a bug that inflates the size of jpegs on upload
+// we get around that by renaming the file ext to .bin
+// see https://github.com/facebook/react-native/issues/27099
+// -prf
+async function withSafeFile<T>(
+  uri: string,
+  fn: (path: string) => Promise<T>,
+): Promise<T> {
+  if (uri.endsWith('.jpeg') || uri.endsWith('.jpg')) {
+    // Since we don't "own" the file, we should avoid renaming or modifying it.
+    // Instead, let's copy it to a temporary file and use that (then remove the
+    // temporary file).
+    const newPath = uri.replace(/\.jpe?g$/, '.bin')
+    try {
+      await RNFS.copyFile(uri, newPath)
+    } catch {
+      // Failed to copy the file, just use the original
+      return await fn(uri)
+    }
+    try {
+      return await fn(newPath)
+    } finally {
+      // Remove the temporary file
+      await RNFS.unlink(newPath)
+    }
+  } else {
+    return fn(uri)
+  }
+}
diff --git a/src/lib/api/upload-blob.web.ts b/src/lib/api/upload-blob.web.ts
new file mode 100644
index 000000000..d3c52190c
--- /dev/null
+++ b/src/lib/api/upload-blob.web.ts
@@ -0,0 +1,26 @@
+import {BskyAgent, ComAtprotoRepoUploadBlob} from '@atproto/api'
+
+/**
+ * @note It is recommended, on web, to use the `file` instance of the file
+ * selector input element, rather than a `data:` URL, to avoid
+ * loading the file into memory. `File` extends `Blob` "file" instances can
+ * be passed directly to this function.
+ */
+export async function uploadBlob(
+  agent: BskyAgent,
+  input: string | Blob,
+  encoding?: string,
+): Promise<ComAtprotoRepoUploadBlob.Response> {
+  if (typeof input === 'string' && input.startsWith('data:')) {
+    const blob = await fetch(input).then(r => r.blob())
+    return agent.uploadBlob(blob, {encoding})
+  }
+
+  if (input instanceof Blob) {
+    return agent.uploadBlob(input, {
+      encoding,
+    })
+  }
+
+  throw new TypeError(`Invalid uploadBlob input: ${typeof input}`)
+}
diff --git a/src/lib/media/manip.ts b/src/lib/media/manip.ts
index 3e647004b..3f01e98c5 100644
--- a/src/lib/media/manip.ts
+++ b/src/lib/media/manip.ts
@@ -218,13 +218,7 @@ export async function safeDeleteAsync(path: string) {
   // Normalize is necessary for Android, otherwise it doesn't delete.
   const normalizedPath = normalizePath(path)
   try {
-    await Promise.allSettled([
-      deleteAsync(normalizedPath, {idempotent: true}),
-      // HACK: Try this one too. Might exist due to api-polyfill hack.
-      deleteAsync(normalizedPath.replace(/\.jpe?g$/, '.bin'), {
-        idempotent: true,
-      }),
-    ])
+    await deleteAsync(normalizedPath, {idempotent: true})
   } catch (e) {
     console.error('Failed to delete file', e)
   }
diff --git a/src/screens/SignupQueued.tsx b/src/screens/SignupQueued.tsx
index 4e4fedcfa..69ef93618 100644
--- a/src/screens/SignupQueued.tsx
+++ b/src/screens/SignupQueued.tsx
@@ -40,7 +40,7 @@ export function SignupQueued() {
       const res = await agent.com.atproto.temp.checkSignupQueue()
       if (res.data.activated) {
         // ready to go, exchange the access token for a usable one and kick off onboarding
-        await agent.refreshSession()
+        await agent.sessionManager.refreshSession()
         if (!isSignupQueued(agent.session?.accessJwt)) {
           onboardingDispatch({type: 'start'})
         }
diff --git a/src/state/queries/preferences/index.ts b/src/state/queries/preferences/index.ts
index 6991f8647..ab866d5e2 100644
--- a/src/state/queries/preferences/index.ts
+++ b/src/state/queries/preferences/index.ts
@@ -37,14 +37,14 @@ export function usePreferencesQuery() {
     refetchOnWindowFocus: true,
     queryKey: preferencesQueryKey,
     queryFn: async () => {
-      if (agent.session?.did === undefined) {
+      if (!agent.did) {
         return DEFAULT_LOGGED_OUT_PREFERENCES
       } else {
         const res = await agent.getPreferences()
 
         // save to local storage to ensure there are labels on initial requests
         saveLabelers(
-          agent.session.did,
+          agent.did,
           res.moderationPrefs.labelers.map(l => l.did),
         )
 
diff --git a/src/state/session/__tests__/session-test.ts b/src/state/session/__tests__/session-test.ts
index 486604169..731b66b0e 100644
--- a/src/state/session/__tests__/session-test.ts
+++ b/src/state/session/__tests__/session-test.ts
@@ -27,7 +27,7 @@ describe('session', () => {
     `)
 
     const agent = new BskyAgent({service: 'https://alice.com'})
-    agent.session = {
+    agent.sessionManager.session = {
       active: true,
       did: 'alice-did',
       handle: 'alice.test',
@@ -118,7 +118,7 @@ describe('session', () => {
     let state = getInitialState([])
 
     const agent1 = new BskyAgent({service: 'https://alice.com'})
-    agent1.session = {
+    agent1.sessionManager.session = {
       active: true,
       did: 'alice-did',
       handle: 'alice.test',
@@ -166,7 +166,7 @@ describe('session', () => {
     `)
 
     const agent2 = new BskyAgent({service: 'https://bob.com'})
-    agent2.session = {
+    agent2.sessionManager.session = {
       active: true,
       did: 'bob-did',
       handle: 'bob.test',
@@ -230,7 +230,7 @@ describe('session', () => {
     `)
 
     const agent3 = new BskyAgent({service: 'https://alice.com'})
-    agent3.session = {
+    agent3.sessionManager.session = {
       active: true,
       did: 'alice-did',
       handle: 'alice-updated.test',
@@ -294,7 +294,7 @@ describe('session', () => {
     `)
 
     const agent4 = new BskyAgent({service: 'https://jay.com'})
-    agent4.session = {
+    agent4.sessionManager.session = {
       active: true,
       did: 'jay-did',
       handle: 'jay.test',
@@ -445,7 +445,7 @@ describe('session', () => {
     let state = getInitialState([])
 
     const agent1 = new BskyAgent({service: 'https://alice.com'})
-    agent1.session = {
+    agent1.sessionManager.session = {
       active: true,
       did: 'alice-did',
       handle: 'alice.test',
@@ -502,7 +502,7 @@ describe('session', () => {
     `)
 
     const agent2 = new BskyAgent({service: 'https://alice.com'})
-    agent2.session = {
+    agent2.sessionManager.session = {
       active: true,
       did: 'alice-did',
       handle: 'alice.test',
@@ -553,7 +553,7 @@ describe('session', () => {
     let state = getInitialState([])
 
     const agent1 = new BskyAgent({service: 'https://alice.com'})
-    agent1.session = {
+    agent1.sessionManager.session = {
       active: true,
       did: 'alice-did',
       handle: 'alice.test',
@@ -598,7 +598,7 @@ describe('session', () => {
     let state = getInitialState([])
 
     const agent1 = new BskyAgent({service: 'https://alice.com'})
-    agent1.session = {
+    agent1.sessionManager.session = {
       active: true,
       did: 'alice-did',
       handle: 'alice.test',
@@ -606,7 +606,7 @@ describe('session', () => {
       refreshJwt: 'alice-refresh-jwt-1',
     }
     const agent2 = new BskyAgent({service: 'https://bob.com'})
-    agent2.session = {
+    agent2.sessionManager.session = {
       active: true,
       did: 'bob-did',
       handle: 'bob.test',
@@ -678,7 +678,7 @@ describe('session', () => {
     let state = getInitialState([])
 
     const agent1 = new BskyAgent({service: 'https://alice.com'})
-    agent1.session = {
+    agent1.sessionManager.session = {
       active: true,
       did: 'alice-did',
       handle: 'alice.test',
@@ -695,7 +695,7 @@ describe('session', () => {
     expect(state.accounts.length).toBe(1)
     expect(state.currentAgentState.did).toBe('alice-did')
 
-    agent1.session = {
+    agent1.sessionManager.session = {
       active: true,
       did: 'alice-did',
       handle: 'alice-updated.test',
@@ -748,7 +748,7 @@ describe('session', () => {
       }
     `)
 
-    agent1.session = {
+    agent1.sessionManager.session = {
       active: true,
       did: 'alice-did',
       handle: 'alice-updated.test',
@@ -801,7 +801,7 @@ describe('session', () => {
       }
     `)
 
-    agent1.session = {
+    agent1.sessionManager.session = {
       active: true,
       did: 'alice-did',
       handle: 'alice-updated.test',
@@ -859,7 +859,7 @@ describe('session', () => {
     let state = getInitialState([])
 
     const agent1 = new BskyAgent({service: 'https://alice.com'})
-    agent1.session = {
+    agent1.sessionManager.session = {
       active: true,
       did: 'alice-did',
       handle: 'alice.test',
@@ -876,7 +876,7 @@ describe('session', () => {
     expect(state.accounts.length).toBe(1)
     expect(state.currentAgentState.did).toBe('alice-did')
 
-    agent1.session = {
+    agent1.sessionManager.session = {
       active: true,
       did: 'alice-did',
       handle: 'alice-updated.test',
@@ -907,7 +907,7 @@ describe('session', () => {
     ])
     expect(lastState === state).toBe(true)
 
-    agent1.session = {
+    agent1.sessionManager.session = {
       active: true,
       did: 'alice-did',
       handle: 'alice-updated.test',
@@ -931,7 +931,7 @@ describe('session', () => {
     let state = getInitialState([])
 
     const agent1 = new BskyAgent({service: 'https://alice.com'})
-    agent1.session = {
+    agent1.sessionManager.session = {
       active: true,
       did: 'alice-did',
       handle: 'alice.test',
@@ -940,7 +940,7 @@ describe('session', () => {
     }
 
     const agent2 = new BskyAgent({service: 'https://bob.com'})
-    agent2.session = {
+    agent2.sessionManager.session = {
       active: true,
       did: 'bob-did',
       handle: 'bob.test',
@@ -965,7 +965,7 @@ describe('session', () => {
     expect(state.accounts.length).toBe(2)
     expect(state.currentAgentState.did).toBe('bob-did')
 
-    agent1.session = {
+    agent1.sessionManager.session = {
       active: true,
       did: 'alice-did',
       handle: 'alice-updated.test',
@@ -1032,7 +1032,7 @@ describe('session', () => {
       }
     `)
 
-    agent2.session = {
+    agent2.sessionManager.session = {
       active: true,
       did: 'bob-did',
       handle: 'bob-updated.test',
@@ -1099,7 +1099,7 @@ describe('session', () => {
 
     // Ignore other events for inactive agent.
     const lastState = state
-    agent1.session = undefined
+    agent1.sessionManager.session = undefined
     state = run(state, [
       {
         type: 'received-agent-event',
@@ -1126,7 +1126,7 @@ describe('session', () => {
     let state = getInitialState([])
 
     const agent1 = new BskyAgent({service: 'https://alice.com'})
-    agent1.session = {
+    agent1.sessionManager.session = {
       active: true,
       did: 'alice-did',
       handle: 'alice.test',
@@ -1135,7 +1135,7 @@ describe('session', () => {
     }
 
     const agent2 = new BskyAgent({service: 'https://bob.com'})
-    agent2.session = {
+    agent2.sessionManager.session = {
       active: true,
       did: 'bob-did',
       handle: 'bob.test',
@@ -1162,7 +1162,7 @@ describe('session', () => {
     expect(state.accounts.length).toBe(1)
     expect(state.currentAgentState.did).toBe('bob-did')
 
-    agent1.session = {
+    agent1.sessionManager.session = {
       active: true,
       did: 'alice-did',
       handle: 'alice.test',
@@ -1188,7 +1188,7 @@ describe('session', () => {
     let state = getInitialState([])
 
     const agent1 = new BskyAgent({service: 'https://alice.com'})
-    agent1.session = {
+    agent1.sessionManager.session = {
       active: true,
       did: 'alice-did',
       handle: 'alice.test',
@@ -1206,7 +1206,7 @@ describe('session', () => {
     expect(state.accounts.length).toBe(1)
     expect(state.currentAgentState.did).toBe('alice-did')
 
-    agent1.session = undefined
+    agent1.sessionManager.session = undefined
     state = run(state, [
       {
         type: 'received-agent-event',
@@ -1255,7 +1255,7 @@ describe('session', () => {
     let state = getInitialState([])
 
     const agent1 = new BskyAgent({service: 'https://alice.com'})
-    agent1.session = {
+    agent1.sessionManager.session = {
       active: true,
       did: 'alice-did',
       handle: 'alice.test',
@@ -1273,7 +1273,7 @@ describe('session', () => {
     expect(state.accounts[0].accessJwt).toBe('alice-access-jwt-1')
     expect(state.currentAgentState.did).toBe('alice-did')
 
-    agent1.session = undefined
+    agent1.sessionManager.session = undefined
     state = run(state, [
       {
         type: 'received-agent-event',
@@ -1320,7 +1320,7 @@ describe('session', () => {
     let state = getInitialState([])
 
     const agent1 = new BskyAgent({service: 'https://alice.com'})
-    agent1.session = {
+    agent1.sessionManager.session = {
       active: true,
       did: 'alice-did',
       handle: 'alice.test',
@@ -1338,7 +1338,7 @@ describe('session', () => {
     expect(state.accounts[0].accessJwt).toBe('alice-access-jwt-1')
     expect(state.currentAgentState.did).toBe('alice-did')
 
-    agent1.session = undefined
+    agent1.sessionManager.session = undefined
     state = run(state, [
       {
         type: 'received-agent-event',
@@ -1385,7 +1385,7 @@ describe('session', () => {
     let state = getInitialState([])
 
     const agent1 = new BskyAgent({service: 'https://alice.com'})
-    agent1.session = {
+    agent1.sessionManager.session = {
       active: true,
       did: 'alice-did',
       handle: 'alice.test',
@@ -1393,7 +1393,7 @@ describe('session', () => {
       refreshJwt: 'alice-refresh-jwt-1',
     }
     const agent2 = new BskyAgent({service: 'https://bob.com'})
-    agent2.session = {
+    agent2.sessionManager.session = {
       active: true,
       did: 'bob-did',
       handle: 'bob.test',
@@ -1416,7 +1416,7 @@ describe('session', () => {
     expect(state.currentAgentState.did).toBe('bob-did')
 
     const anotherTabAgent1 = new BskyAgent({service: 'https://jay.com'})
-    anotherTabAgent1.session = {
+    anotherTabAgent1.sessionManager.session = {
       active: true,
       did: 'jay-did',
       handle: 'jay.test',
@@ -1424,7 +1424,7 @@ describe('session', () => {
       refreshJwt: 'jay-refresh-jwt-1',
     }
     const anotherTabAgent2 = new BskyAgent({service: 'https://alice.com'})
-    anotherTabAgent2.session = {
+    anotherTabAgent2.sessionManager.session = {
       active: true,
       did: 'bob-did',
       handle: 'bob.test',
@@ -1492,7 +1492,7 @@ describe('session', () => {
     `)
 
     const anotherTabAgent3 = new BskyAgent({service: 'https://clarence.com'})
-    anotherTabAgent3.session = {
+    anotherTabAgent3.sessionManager.session = {
       active: true,
       did: 'clarence-did',
       handle: 'clarence.test',
diff --git a/src/state/session/agent.ts b/src/state/session/agent.ts
index 4456ab0bf..73be34bb2 100644
--- a/src/state/session/agent.ts
+++ b/src/state/session/agent.ts
@@ -1,4 +1,9 @@
-import {AtpSessionData, AtpSessionEvent, BskyAgent} from '@atproto/api'
+import {
+  AtpPersistSessionHandler,
+  AtpSessionData,
+  AtpSessionEvent,
+  BskyAgent,
+} from '@atproto/api'
 import {TID} from '@atproto/common-web'
 
 import {networkRetry} from '#/lib/async/retry'
@@ -20,6 +25,8 @@ import {
 import {SessionAccount} from './types'
 import {isSessionExpired, isSignupQueued} from './util'
 
+type SetPersistSessionHandler = (cb: AtpPersistSessionHandler) => void
+
 export function createPublicAgent() {
   configureModerationForGuest() // Side effect but only relevant for tests
   return new BskyAgent({service: PUBLIC_BSKY_SERVICE})
@@ -32,10 +39,11 @@ export async function createAgentAndResume(
     did: string,
     event: AtpSessionEvent,
   ) => void,
+  setPersistSessionHandler: SetPersistSessionHandler,
 ) {
   const agent = new BskyAgent({service: storedAccount.service})
   if (storedAccount.pdsUrl) {
-    agent.pdsUrl = agent.api.xrpc.uri = new URL(storedAccount.pdsUrl)
+    agent.sessionManager.pdsUrl = new URL(storedAccount.pdsUrl)
   }
   const gates = tryFetchGates(storedAccount.did, 'prefer-low-latency')
   const moderation = configureModerationForAccount(agent, storedAccount)
@@ -43,9 +51,8 @@ export async function createAgentAndResume(
   if (isSessionExpired(storedAccount)) {
     await networkRetry(1, () => agent.resumeSession(prevSession))
   } else {
-    agent.session = prevSession
+    agent.sessionManager.session = prevSession
     if (!storedAccount.signupQueued) {
-      // Intentionally not awaited to unblock the UI:
       networkRetry(3, () => agent.resumeSession(prevSession)).catch(
         (e: any) => {
           logger.error(`networkRetry failed to resume session`, {
@@ -60,7 +67,13 @@ export async function createAgentAndResume(
     }
   }
 
-  return prepareAgent(agent, gates, moderation, onSessionChange)
+  return prepareAgent(
+    agent,
+    gates,
+    moderation,
+    onSessionChange,
+    setPersistSessionHandler,
+  )
 }
 
 export async function createAgentAndLogin(
@@ -80,6 +93,7 @@ export async function createAgentAndLogin(
     did: string,
     event: AtpSessionEvent,
   ) => void,
+  setPersistSessionHandler: SetPersistSessionHandler,
 ) {
   const agent = new BskyAgent({service})
   await agent.login({identifier, password, authFactorToken})
@@ -87,7 +101,13 @@ export async function createAgentAndLogin(
   const account = agentToSessionAccountOrThrow(agent)
   const gates = tryFetchGates(account.did, 'prefer-fresh-gates')
   const moderation = configureModerationForAccount(agent, account)
-  return prepareAgent(agent, moderation, gates, onSessionChange)
+  return prepareAgent(
+    agent,
+    moderation,
+    gates,
+    onSessionChange,
+    setPersistSessionHandler,
+  )
 }
 
 export async function createAgentAndCreateAccount(
@@ -115,6 +135,7 @@ export async function createAgentAndCreateAccount(
     did: string,
     event: AtpSessionEvent,
   ) => void,
+  setPersistSessionHandler: SetPersistSessionHandler,
 ) {
   const agent = new BskyAgent({service})
   await agent.createAccount({
@@ -174,7 +195,13 @@ export async function createAgentAndCreateAccount(
     logger.error(e, {context: `session: failed snoozeEmailConfirmationPrompt`})
   }
 
-  return prepareAgent(agent, gates, moderation, onSessionChange)
+  return prepareAgent(
+    agent,
+    gates,
+    moderation,
+    onSessionChange,
+    setPersistSessionHandler,
+  )
 }
 
 async function prepareAgent(
@@ -187,13 +214,14 @@ async function prepareAgent(
     did: string,
     event: AtpSessionEvent,
   ) => void,
+  setPersistSessionHandler: (cb: AtpPersistSessionHandler) => void,
 ) {
   // There's nothing else left to do, so block on them here.
   await Promise.all([gates, moderation])
 
   // Now the agent is ready.
   const account = agentToSessionAccountOrThrow(agent)
-  agent.setPersistSessionHandler(event => {
+  setPersistSessionHandler(event => {
     onSessionChange(agent, account.did, event)
     if (event !== 'create' && event !== 'update') {
       addSessionErrorLog(account.did, event)
diff --git a/src/state/session/index.tsx b/src/state/session/index.tsx
index 09fcf8664..4f01f7165 100644
--- a/src/state/session/index.tsx
+++ b/src/state/session/index.tsx
@@ -1,5 +1,9 @@
 import React from 'react'
-import {AtpSessionEvent, BskyAgent} from '@atproto/api'
+import {
+  AtpPersistSessionHandler,
+  AtpSessionEvent,
+  BskyAgent,
+} from '@atproto/api'
 
 import {track} from '#/lib/analytics/analytics'
 import {logEvent} from '#/lib/statsig/statsig'
@@ -47,6 +51,15 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
     return initialState
   })
 
+  const persistSessionHandler = React.useRef<
+    AtpPersistSessionHandler | undefined
+  >(undefined)
+  const setPersistSessionHandler = (
+    newHandler: AtpPersistSessionHandler | undefined,
+  ) => {
+    persistSessionHandler.current = newHandler
+  }
+
   const onAgentSessionChange = React.useCallback(
     (agent: BskyAgent, accountDid: string, sessionEvent: AtpSessionEvent) => {
       const refreshedAccount = agentToSessionAccount(agent) // Mutable, so snapshot it right away.
@@ -73,6 +86,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
       const {agent, account} = await createAgentAndCreateAccount(
         params,
         onAgentSessionChange,
+        setPersistSessionHandler,
       )
 
       if (signal.aborted) {
@@ -97,6 +111,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
       const {agent, account} = await createAgentAndLogin(
         params,
         onAgentSessionChange,
+        setPersistSessionHandler,
       )
 
       if (signal.aborted) {
@@ -138,6 +153,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
       const {agent, account} = await createAgentAndResume(
         storedAccount,
         onAgentSessionChange,
+        setPersistSessionHandler,
       )
 
       if (signal.aborted) {
@@ -202,7 +218,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
         } else {
           const agent = state.currentAgentState.agent as BskyAgent
           const prevSession = agent.session
-          agent.session = sessionAccountToSession(syncedAccount)
+          agent.sessionManager.session = sessionAccountToSession(syncedAccount)
           addSessionDebugLog({
             type: 'agent:patch',
             agent,
@@ -249,8 +265,8 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
       addSessionDebugLog({type: 'agent:switch', prevAgent, nextAgent: agent})
       // We never reuse agents so let's fully neutralize the previous one.
       // This ensures it won't try to consume any refresh tokens.
-      prevAgent.session = undefined
-      prevAgent.setPersistSessionHandler(undefined)
+      prevAgent.sessionManager.session = undefined
+      setPersistSessionHandler(undefined)
     }
   }, [agent])
 
diff --git a/src/state/session/logging.ts b/src/state/session/logging.ts
index b57f1fa0b..7e1df500b 100644
--- a/src/state/session/logging.ts
+++ b/src/state/session/logging.ts
@@ -56,7 +56,7 @@ type Log =
       type: 'agent:patch'
       agent: object
       prevSession: AtpSessionData | undefined
-      nextSession: AtpSessionData
+      nextSession: AtpSessionData | undefined
     }
 
 export function wrapSessionReducerForLogging(reducer: Reducer): Reducer {
diff --git a/yarn.lock b/yarn.lock
index ba1227f30..cd0508d6a 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -34,39 +34,65 @@
     jsonpointer "^5.0.0"
     leven "^3.1.0"
 
-"@atproto/api@0.12.29":
-  version "0.12.29"
-  resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.12.29.tgz#95a19202c2f0eec4c955909685be11009ba9b9a1"
-  integrity sha512-PyzPLjGWR0qNOMrmj3Nt3N5NuuANSgOk/33Bu3j+rFjjPrHvk9CI6iQPU6zuDaDCoyOTRJRafw8X/aMQw+ilgw==
+"@atproto-labs/fetch-node@0.1.0":
+  version "0.1.0"
+  resolved "https://registry.yarnpkg.com/@atproto-labs/fetch-node/-/fetch-node-0.1.0.tgz#692666d57ec24a7ba0813077a303baccf26108e0"
+  integrity sha512-DUHgaGw8LBqiGg51pUDuWK/alMcmNbpcK7ALzlF2Gw//TNLTsgrj0qY9aEtK+np9rEC+x/o3bN4SGnuQEpgqIg==
   dependencies:
-    "@atproto/common-web" "^0.3.0"
-    "@atproto/lexicon" "^0.4.0"
-    "@atproto/syntax" "^0.3.0"
-    "@atproto/xrpc" "^0.5.0"
-    await-lock "^2.2.2"
-    multiformats "^9.9.0"
-    tlds "^1.234.0"
+    "@atproto-labs/fetch" "0.1.0"
+    "@atproto-labs/pipe" "0.1.0"
+    ipaddr.js "^2.1.0"
+    psl "^1.9.0"
+    undici "^6.14.1"
+
+"@atproto-labs/fetch@0.1.0":
+  version "0.1.0"
+  resolved "https://registry.yarnpkg.com/@atproto-labs/fetch/-/fetch-0.1.0.tgz#50a46943fd2f321dd748de28c73ba7cbfa493132"
+  integrity sha512-uirja+uA/C4HNk7vayM+AJqsccxQn2wVziUHxbsjJGt/K6Q8ZOKDaEX2+GrcXvpUVcqUKh+94JFjuzH+CAEUlg==
+  dependencies:
+    "@atproto-labs/pipe" "0.1.0"
+  optionalDependencies:
+    zod "^3.23.8"
+
+"@atproto-labs/pipe@0.1.0":
+  version "0.1.0"
+  resolved "https://registry.yarnpkg.com/@atproto-labs/pipe/-/pipe-0.1.0.tgz#c8d86923b6d8e900d39efe6fdcdf0d897c434086"
+  integrity sha512-ghOqHFyJlQVFPESzlVHjKroP0tPzbmG5Jms0dNI9yLDEfL8xp4OFPWLX4f6T8mRq69wWs4nIDM3sSsFbFqLa1w==
+
+"@atproto-labs/simple-store-memory@0.1.1":
+  version "0.1.1"
+  resolved "https://registry.yarnpkg.com/@atproto-labs/simple-store-memory/-/simple-store-memory-0.1.1.tgz#54526a1f8ec978822be9fad75106ad8b78500dd3"
+  integrity sha512-PCRqhnZ8NBNBvLku53O56T0lsVOtclfIrQU/rwLCc4+p45/SBPrRYNBi6YFq5rxZbK6Njos9MCmILV/KLQxrWA==
+  dependencies:
+    "@atproto-labs/simple-store" "0.1.1"
+    lru-cache "^10.2.0"
+
+"@atproto-labs/simple-store@0.1.1":
+  version "0.1.1"
+  resolved "https://registry.yarnpkg.com/@atproto-labs/simple-store/-/simple-store-0.1.1.tgz#e743a2722b5d8732166f0a72aca8bd10e9bff106"
+  integrity sha512-WKILW2b3QbAYKh+w5U2x6p5FqqLl0nAeLwGeDY+KjX01K4Dq3vQTR9b/qNp0jZm48CabPQVrqCv0PPU9LgRRRg==
 
-"@atproto/api@^0.12.3":
-  version "0.12.3"
-  resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.12.3.tgz#5b7b1c7d4210ee9315961504900c8409395cbb17"
-  integrity sha512-y/kGpIEo+mKGQ7VOphpqCAigTI0LZRmDThNChTfSzDKm9TzEobwiw0zUID0Yw6ot1iLLFx3nKURmuZAYlEuobw==
+"@atproto/api@0.13.0", "@atproto/api@^0.13.0":
+  version "0.13.0"
+  resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.13.0.tgz#d1c65a407f1c3c6aba5be9425f4f739a01419bd8"
+  integrity sha512-04kzIDkoEVSP7zMVOT5ezCVQcOrbXWjGYO2YBc3/tBvQ90V1pl9I+mLyz1uUHE+wRE1IRWKACcWhAz8SrYz3pA==
   dependencies:
     "@atproto/common-web" "^0.3.0"
-    "@atproto/lexicon" "^0.4.0"
+    "@atproto/lexicon" "^0.4.1"
     "@atproto/syntax" "^0.3.0"
-    "@atproto/xrpc" "^0.5.0"
+    "@atproto/xrpc" "^0.6.0"
+    await-lock "^2.2.2"
     multiformats "^9.9.0"
     tlds "^1.234.0"
 
-"@atproto/aws@^0.2.0":
-  version "0.2.0"
-  resolved "https://registry.yarnpkg.com/@atproto/aws/-/aws-0.2.0.tgz#17f3faf744824457cabd62f87be8bf08cacf8029"
-  integrity sha512-F09SHiC9CX3ydfrvYZbkpfES48UGCQNnznNVgJ3QyKSN8ON+BoWmGCpAFtn3AWeEoU0w9h0hypNvUm5nORv+5g==
+"@atproto/aws@^0.2.2":
+  version "0.2.2"
+  resolved "https://registry.yarnpkg.com/@atproto/aws/-/aws-0.2.2.tgz#703e5e06f288bcf61c6d99a990738f1e7299e653"
+  integrity sha512-j7eR7+sQumFsc66/5xyCDez9JtR6dlZc+fOdwdh85nCJD4zmQyU4r1CKrA48wQ3tkzze+ASEb1SgODuIQmIugA==
   dependencies:
-    "@atproto/common" "^0.4.0"
+    "@atproto/common" "^0.4.1"
     "@atproto/crypto" "^0.4.0"
-    "@atproto/repo" "^0.4.0"
+    "@atproto/repo" "^0.4.2"
     "@aws-sdk/client-cloudfront" "^3.261.0"
     "@aws-sdk/client-kms" "^3.196.0"
     "@aws-sdk/client-s3" "^3.224.0"
@@ -76,19 +102,19 @@
     multiformats "^9.9.0"
     uint8arrays "3.0.0"
 
-"@atproto/bsky@^0.0.45":
-  version "0.0.45"
-  resolved "https://registry.yarnpkg.com/@atproto/bsky/-/bsky-0.0.45.tgz#c3083d8038fe8c5ff921d9bcb0b5a043cc840827"
-  integrity sha512-osWeigdYzQH2vZki+eszCR8ta9zdUB4om79aFmnE+zvxw7HFduwAAbcHf6kmmiLCfaOWvCsYb1wS2i3IC66TAg==
+"@atproto/bsky@^0.0.74":
+  version "0.0.74"
+  resolved "https://registry.yarnpkg.com/@atproto/bsky/-/bsky-0.0.74.tgz#b735af6ded16778604378710a2e871350c29570a"
+  integrity sha512-vyukmlBamoET0sZnDMOeTGAkQNV7KbHg65uIQ6OX4/QGynyaQP8SvSF0OsEBzBqOraxV1w9WT8AZrUbyl3uvIg==
   dependencies:
-    "@atproto/api" "^0.12.3"
-    "@atproto/common" "^0.4.0"
+    "@atproto/api" "^0.13.0"
+    "@atproto/common" "^0.4.1"
     "@atproto/crypto" "^0.4.0"
     "@atproto/identity" "^0.4.0"
-    "@atproto/lexicon" "^0.4.0"
-    "@atproto/repo" "^0.4.0"
+    "@atproto/lexicon" "^0.4.1"
+    "@atproto/repo" "^0.4.2"
     "@atproto/syntax" "^0.3.0"
-    "@atproto/xrpc-server" "^0.5.1"
+    "@atproto/xrpc-server" "^0.6.1"
     "@bufbuild/protobuf" "^1.5.0"
     "@connectrpc/connect" "^1.1.4"
     "@connectrpc/connect-express" "^1.1.4"
@@ -105,19 +131,20 @@
     multiformats "^9.9.0"
     p-queue "^6.6.2"
     pg "^8.10.0"
-    pino "^8.15.0"
+    pino "^8.21.0"
     pino-http "^8.2.1"
     sharp "^0.32.6"
+    statsig-node "^5.23.1"
     structured-headers "^1.0.1"
     typed-emitter "^2.1.0"
     uint8arrays "3.0.0"
 
-"@atproto/bsync@^0.0.3":
-  version "0.0.3"
-  resolved "https://registry.yarnpkg.com/@atproto/bsync/-/bsync-0.0.3.tgz#2b0b8ef3686cf177846a80088317f2e89d1bf88f"
-  integrity sha512-tJRwNgXzfNV57lzgWPvjtb1OMlMJH9SpsMeYhIii16zcaFUWwsb474BicKpkGRT+iCvtYzBT6gWlZE2Ijnhf7w==
+"@atproto/bsync@^0.0.5":
+  version "0.0.5"
+  resolved "https://registry.yarnpkg.com/@atproto/bsync/-/bsync-0.0.5.tgz#bf2fa45e4595fda12addcd6784314e4dbe409046"
+  integrity sha512-xCCMHy14y4tQoXiGrfd0XjSnc4q7I9bUNqju9E8jrP95QTDedH1FQgybStbUIbHt0eEqY5v9E7iZBH3n7Kiz7A==
   dependencies:
-    "@atproto/common" "^0.4.0"
+    "@atproto/common" "^0.4.1"
     "@atproto/syntax" "^0.3.0"
     "@bufbuild/protobuf" "^1.5.0"
     "@connectrpc/connect" "^1.1.4"
@@ -158,17 +185,17 @@
     pino "^8.6.1"
     zod "^3.14.2"
 
-"@atproto/common@^0.4.0":
-  version "0.4.0"
-  resolved "https://registry.yarnpkg.com/@atproto/common/-/common-0.4.0.tgz#d77696c7eb545426df727837d9ee333b429fe7ef"
-  integrity sha512-yOXuPlCjT/OK9j+neIGYn9wkxx/AlxQSucysAF0xgwu0Ji8jAtKBf9Jv6R5ObYAjAD/kVUvEYumle+Yq/R9/7g==
+"@atproto/common@^0.4.1":
+  version "0.4.1"
+  resolved "https://registry.yarnpkg.com/@atproto/common/-/common-0.4.1.tgz#ca6fce47001ce8d031acd3fb4942fbfd81f72c43"
+  integrity sha512-uL7kQIcBTbvkBDNfxMXL6lBH4fO2DQpHd2BryJxMtbw/4iEPKe9xBYApwECHhEIk9+zhhpTRZ15FJ3gxTXN82Q==
   dependencies:
     "@atproto/common-web" "^0.3.0"
     "@ipld/dag-cbor" "^7.0.3"
     cbor-x "^1.5.1"
     iso-datestring-validator "^2.2.2"
     multiformats "^9.9.0"
-    pino "^8.15.0"
+    pino "^8.21.0"
 
 "@atproto/crypto@0.1.0":
   version "0.1.0"
@@ -190,22 +217,22 @@
     "@noble/hashes" "^1.3.1"
     uint8arrays "3.0.0"
 
-"@atproto/dev-env@^0.3.5":
-  version "0.3.5"
-  resolved "https://registry.yarnpkg.com/@atproto/dev-env/-/dev-env-0.3.5.tgz#cd13313dbc52131731d039a1d22808ee8193505d"
-  integrity sha512-dqRNihzX1xIHbWPHmfYsliUUXyZn5FFhCeButrGie5soQmHA4okQJTB1XWDly3mdHLjUM90g+5zjRSAKoui77Q==
+"@atproto/dev-env@^0.3.39":
+  version "0.3.39"
+  resolved "https://registry.yarnpkg.com/@atproto/dev-env/-/dev-env-0.3.39.tgz#f498f087d4da43d5f86805c07d5f2b781e60fd6f"
+  integrity sha512-rIeUO99DL8/gRKYEAkAFuTn77y8letEbKMXnfpsVX2YHD89VRdDyMxkYzRu2+31UjtGv62I+qTLLKQS4EcFItA==
   dependencies:
-    "@atproto/api" "^0.12.3"
-    "@atproto/bsky" "^0.0.45"
-    "@atproto/bsync" "^0.0.3"
+    "@atproto/api" "^0.13.0"
+    "@atproto/bsky" "^0.0.74"
+    "@atproto/bsync" "^0.0.5"
     "@atproto/common-web" "^0.3.0"
     "@atproto/crypto" "^0.4.0"
     "@atproto/identity" "^0.4.0"
-    "@atproto/lexicon" "^0.4.0"
-    "@atproto/ozone" "^0.1.7"
-    "@atproto/pds" "^0.4.14"
+    "@atproto/lexicon" "^0.4.1"
+    "@atproto/ozone" "^0.1.36"
+    "@atproto/pds" "^0.4.48"
     "@atproto/syntax" "^0.3.0"
-    "@atproto/xrpc-server" "^0.5.1"
+    "@atproto/xrpc-server" "^0.6.1"
     "@did-plc/lib" "^0.0.1"
     "@did-plc/server" "^0.0.1"
     axios "^0.27.2"
@@ -224,30 +251,79 @@
     "@atproto/crypto" "^0.4.0"
     axios "^0.27.2"
 
-"@atproto/lexicon@^0.4.0":
-  version "0.4.0"
-  resolved "https://registry.yarnpkg.com/@atproto/lexicon/-/lexicon-0.4.0.tgz#63e8829945d80c25524882caa8ed27b1151cc576"
-  integrity sha512-RvCBKdSI4M8qWm5uTNz1z3R2yIvIhmOsMuleOj8YR6BwRD+QbtUBy3l+xQ7iXf4M5fdfJFxaUNa6Ty0iRwdKqQ==
+"@atproto/jwk-jose@0.1.2":
+  version "0.1.2"
+  resolved "https://registry.yarnpkg.com/@atproto/jwk-jose/-/jwk-jose-0.1.2.tgz#236eadb740b498689d9a912d1254aa9ff58890a1"
+  integrity sha512-lDwc/6lLn2aZ/JpyyggyjLFsJPMntrVzryyGUx5aNpuTS8SIuc4Ky0REhxqfLopQXJJZCuRRjagHG3uP05/moQ==
+  dependencies:
+    "@atproto/jwk" "0.1.1"
+    jose "^5.2.0"
+
+"@atproto/jwk@0.1.1":
+  version "0.1.1"
+  resolved "https://registry.yarnpkg.com/@atproto/jwk/-/jwk-0.1.1.tgz#15bcad4a1778eeb20c82108e0ec55fef45cd07b6"
+  integrity sha512-6h/bj1APUk7QcV9t/oA6+9DB5NZx9SZru9x+/pV5oHFI9Xz4ZuM5+dq1PfsJV54pZyqdnZ6W6M717cxoC7q7og==
+  dependencies:
+    multiformats "^9.9.0"
+    zod "^3.23.8"
+
+"@atproto/lexicon@^0.4.1":
+  version "0.4.1"
+  resolved "https://registry.yarnpkg.com/@atproto/lexicon/-/lexicon-0.4.1.tgz#19155210570a2fafbcc7d4f655d9b813948e72a0"
+  integrity sha512-bzyr+/VHXLQWbumViX5L7h1NKQObfs8Z+XZJl43OUK8nYFUI4e/sW1IZKRNfw7Wvi5YVNK+J+yP3DWIBZhkCYA==
   dependencies:
     "@atproto/common-web" "^0.3.0"
     "@atproto/syntax" "^0.3.0"
     iso-datestring-validator "^2.2.2"
     multiformats "^9.9.0"
-    zod "^3.21.4"
+    zod "^3.23.8"
 
-"@atproto/ozone@^0.1.7":
-  version "0.1.7"
-  resolved "https://registry.yarnpkg.com/@atproto/ozone/-/ozone-0.1.7.tgz#248d88e1acfe56936651754975472d03d047d689"
-  integrity sha512-vvaV0MFynOzZJcL8m8mEW21o1FFIkP+wHTXEC9LJrL3h03+PMaby8Ujmif6WX5eikhfxvr9xsU/Jxbi/iValuQ==
+"@atproto/oauth-provider@^0.1.2":
+  version "0.1.2"
+  resolved "https://registry.yarnpkg.com/@atproto/oauth-provider/-/oauth-provider-0.1.2.tgz#a576a4c7795c7938a994e76192c19a2e73ffcddf"
+  integrity sha512-z1YKK0XLDfSDtLP5ntPCviEtajvUHbI4TwzYQ5X9CAL9PoXjqhQg0U/csg1wGDs8qkbphF9gni9M2stlpH7H0g==
+  dependencies:
+    "@atproto-labs/fetch" "0.1.0"
+    "@atproto-labs/fetch-node" "0.1.0"
+    "@atproto-labs/pipe" "0.1.0"
+    "@atproto-labs/simple-store" "0.1.1"
+    "@atproto-labs/simple-store-memory" "0.1.1"
+    "@atproto/jwk" "0.1.1"
+    "@atproto/jwk-jose" "0.1.2"
+    "@atproto/oauth-types" "0.1.2"
+    "@hapi/accept" "^6.0.3"
+    "@hapi/bourne" "^3.0.0"
+    cookie "^0.6.0"
+    http-errors "^2.0.0"
+    jose "^5.2.0"
+    oidc-token-hash "^5.0.3"
+    psl "^1.9.0"
+    zod "^3.23.8"
+  optionalDependencies:
+    ioredis "^5.3.2"
+    keygrip "^1.1.0"
+
+"@atproto/oauth-types@0.1.2":
+  version "0.1.2"
+  resolved "https://registry.yarnpkg.com/@atproto/oauth-types/-/oauth-types-0.1.2.tgz#d6c497c8e5f88f1875c630adde4ed9c5d8a8b4f4"
+  integrity sha512-yySPPTLxteFJ3O3xVWEhvBFx7rczgo4LK2nQNeqAPMZdYd5dpgvuZZ88nQQge074BfuOc0MWTnr0kPdxQMjjPw==
+  dependencies:
+    "@atproto/jwk" "0.1.1"
+    zod "^3.23.8"
+
+"@atproto/ozone@^0.1.36":
+  version "0.1.36"
+  resolved "https://registry.yarnpkg.com/@atproto/ozone/-/ozone-0.1.36.tgz#6a1a71fdff3ff486c5951a9e491e954b51703d53"
+  integrity sha512-BQThLU5RFG+/bZli/fj5YrFU8jW5rkium7aplfJX2eHkV6huJnBU5DcgracjH2paPGC5L/zjYtibz5spqatKAg==
   dependencies:
-    "@atproto/api" "^0.12.3"
-    "@atproto/common" "^0.4.0"
+    "@atproto/api" "^0.13.0"
+    "@atproto/common" "^0.4.1"
     "@atproto/crypto" "^0.4.0"
     "@atproto/identity" "^0.4.0"
-    "@atproto/lexicon" "^0.4.0"
+    "@atproto/lexicon" "^0.4.1"
     "@atproto/syntax" "^0.3.0"
-    "@atproto/xrpc" "^0.5.0"
-    "@atproto/xrpc-server" "^0.5.1"
+    "@atproto/xrpc" "^0.6.0"
+    "@atproto/xrpc-server" "^0.6.1"
     "@did-plc/lib" "^0.0.1"
     axios "^1.6.7"
     compression "^1.7.4"
@@ -255,30 +331,34 @@
     express "^4.17.2"
     http-terminator "^3.2.0"
     kysely "^0.22.0"
+    lande "^1.0.10"
     multiformats "^9.9.0"
     p-queue "^6.6.2"
     pg "^8.10.0"
     pino-http "^8.2.1"
+    structured-headers "^1.0.1"
     typed-emitter "^2.1.0"
     uint8arrays "3.0.0"
 
-"@atproto/pds@^0.4.14":
-  version "0.4.14"
-  resolved "https://registry.yarnpkg.com/@atproto/pds/-/pds-0.4.14.tgz#5b55ef307323bda712f2ddaba5c1fff7740ed91b"
-  integrity sha512-rqVcvtw5oMuuJIpWZbSSTSx19+JaZyUcg9OEjdlUmyEpToRN88zTEQySEksymrrLQkW/LPRyWGd7WthbGEuEfQ==
+"@atproto/pds@^0.4.48":
+  version "0.4.48"
+  resolved "https://registry.yarnpkg.com/@atproto/pds/-/pds-0.4.48.tgz#34f29846a0585f5cc33f1685eb75ad730b7dcb9f"
+  integrity sha512-B5FpmECkGtA0EyhiB5rfhmQArmGekqqyzFnPlNpO5vOUrTTVKc9mgGfHLVJtrnwDUfGAuIgpigqZ8HgwS0DnMA==
   dependencies:
-    "@atproto/api" "^0.12.3"
-    "@atproto/aws" "^0.2.0"
-    "@atproto/common" "^0.4.0"
+    "@atproto-labs/fetch-node" "0.1.0"
+    "@atproto/api" "^0.13.0"
+    "@atproto/aws" "^0.2.2"
+    "@atproto/common" "^0.4.1"
     "@atproto/crypto" "^0.4.0"
     "@atproto/identity" "^0.4.0"
-    "@atproto/lexicon" "^0.4.0"
-    "@atproto/repo" "^0.4.0"
+    "@atproto/lexicon" "^0.4.1"
+    "@atproto/oauth-provider" "^0.1.2"
+    "@atproto/repo" "^0.4.2"
     "@atproto/syntax" "^0.3.0"
-    "@atproto/xrpc" "^0.5.0"
-    "@atproto/xrpc-server" "^0.5.1"
+    "@atproto/xrpc" "^0.6.0"
+    "@atproto/xrpc-server" "^0.6.1"
     "@did-plc/lib" "^0.0.4"
-    better-sqlite3 "^9.4.0"
+    better-sqlite3 "^10.0.0"
     bytes "^3.1.2"
     compression "^1.7.4"
     cors "^2.8.5"
@@ -297,41 +377,42 @@
     nodemailer "^6.8.0"
     nodemailer-html-to-text "^3.2.0"
     p-queue "^6.6.2"
-    pino "^8.15.0"
+    pino "^8.21.0"
     pino-http "^8.2.1"
     sharp "^0.32.6"
     typed-emitter "^2.1.0"
     uint8arrays "3.0.0"
-    zod "^3.21.4"
+    zod "^3.23.8"
 
-"@atproto/repo@^0.4.0":
-  version "0.4.0"
-  resolved "https://registry.yarnpkg.com/@atproto/repo/-/repo-0.4.0.tgz#e5d3195a8e4233c9bf060737b18ddee905af2d9a"
-  integrity sha512-LB0DF/D8r8hB+qiGB0sWZuq7TSJYbWel+t572aCrLeCOmbRgnLkGPLUTOOUvLFYv8xz1BPZTbI8hy/vcUV79VA==
+"@atproto/repo@^0.4.2":
+  version "0.4.2"
+  resolved "https://registry.yarnpkg.com/@atproto/repo/-/repo-0.4.2.tgz#311eef52ef5df0b6f969fb4b329935a32db05313"
+  integrity sha512-6hEGA3BmasPCoBGaIN/jKAjKJidCf+z8exkx/77V3WB7TboucSLHn/8gg+Xf03U7bJd6mn3F0YmPaRfJwqIT8w==
   dependencies:
-    "@atproto/common" "^0.4.0"
+    "@atproto/common" "^0.4.1"
     "@atproto/common-web" "^0.3.0"
     "@atproto/crypto" "^0.4.0"
-    "@atproto/lexicon" "^0.4.0"
+    "@atproto/lexicon" "^0.4.1"
     "@ipld/car" "^3.2.3"
     "@ipld/dag-cbor" "^7.0.0"
     multiformats "^9.9.0"
     uint8arrays "3.0.0"
-    zod "^3.21.4"
+    zod "^3.23.8"
 
 "@atproto/syntax@^0.3.0":
   version "0.3.0"
   resolved "https://registry.yarnpkg.com/@atproto/syntax/-/syntax-0.3.0.tgz#fafa2dbea9add37253005cb663e7373e05e618b3"
   integrity sha512-Weq0ZBxffGHDXHl9U7BQc2BFJi/e23AL+k+i5+D9hUq/bzT4yjGsrCejkjq0xt82xXDjmhhvQSZ0LqxyZ5woxA==
 
-"@atproto/xrpc-server@^0.5.1":
-  version "0.5.1"
-  resolved "https://registry.yarnpkg.com/@atproto/xrpc-server/-/xrpc-server-0.5.1.tgz#f63c86ba60bd5b9c5a641ea57191ff83d9db41fd"
-  integrity sha512-SXU6dscVe5iYxPeV79QIFs/yEEu7LLOzyHGoHG1kSNO6DjwxXTdcWOc8GSYGV6H+7VycOoPZPkyD9q4teJlj/w==
+"@atproto/xrpc-server@^0.6.1":
+  version "0.6.1"
+  resolved "https://registry.yarnpkg.com/@atproto/xrpc-server/-/xrpc-server-0.6.1.tgz#c8c75065ab6bc1a7f5c121b558acb5213f2afda6"
+  integrity sha512-Qm0aJC1LbYYHaRGWoh0D2iG48VwRha1T1NEP/D5UkD4GzfjT8m5PDiZBtcyspJD/BEC7UYX9/BhMYCoZLQMYcA==
   dependencies:
-    "@atproto/common" "^0.4.0"
+    "@atproto/common" "^0.4.1"
     "@atproto/crypto" "^0.4.0"
-    "@atproto/lexicon" "^0.4.0"
+    "@atproto/lexicon" "^0.4.1"
+    "@atproto/xrpc" "^0.6.0"
     cbor-x "^1.5.1"
     express "^4.17.2"
     http-errors "^2.0.0"
@@ -339,15 +420,15 @@
     rate-limiter-flexible "^2.4.1"
     uint8arrays "3.0.0"
     ws "^8.12.0"
-    zod "^3.21.4"
+    zod "^3.23.8"
 
-"@atproto/xrpc@^0.5.0":
-  version "0.5.0"
-  resolved "https://registry.yarnpkg.com/@atproto/xrpc/-/xrpc-0.5.0.tgz#dacbfd8f7b13f0ab5bd56f8fdd4b460e132a6032"
-  integrity sha512-swu+wyOLvYW4l3n+VAuJbHcPcES+tin2Lsrp8Bw5aIXIICiuFn1YMFlwK9JwVUzTH21Py1s1nHEjr4CJeElJog==
+"@atproto/xrpc@^0.6.0":
+  version "0.6.0"
+  resolved "https://registry.yarnpkg.com/@atproto/xrpc/-/xrpc-0.6.0.tgz#668c3262e67e2afa65951ea79a03bfe3720ddf5c"
+  integrity sha512-5BbhBTv5j6MC3iIQ4+vYxQE7nLy2dDGQ+LYJrH8PptOCUdq0Pwg6aRccQ3y52kUZlhE/mzOTZ8Ngiy9pSAyfVQ==
   dependencies:
-    "@atproto/lexicon" "^0.4.0"
-    zod "^3.21.4"
+    "@atproto/lexicon" "^0.4.1"
+    zod "^3.23.8"
 
 "@aws-crypto/crc32@3.0.0":
   version "3.0.0"
@@ -4001,6 +4082,31 @@
   resolved "https://registry.yarnpkg.com/@graphql-typed-document-node/core/-/core-3.2.0.tgz#5f3d96ec6b2354ad6d8a28bf216a1d97b5426861"
   integrity sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==
 
+"@hapi/accept@^6.0.3":
+  version "6.0.3"
+  resolved "https://registry.yarnpkg.com/@hapi/accept/-/accept-6.0.3.tgz#eef0800a4f89cd969da8e5d0311dc877c37279ab"
+  integrity sha512-p72f9k56EuF0n3MwlBNThyVE5PXX40g+aQh+C/xbKrfzahM2Oispv3AXmOIU51t3j77zay1qrX7IIziZXspMlw==
+  dependencies:
+    "@hapi/boom" "^10.0.1"
+    "@hapi/hoek" "^11.0.2"
+
+"@hapi/boom@^10.0.1":
+  version "10.0.1"
+  resolved "https://registry.yarnpkg.com/@hapi/boom/-/boom-10.0.1.tgz#ebb14688275ae150aa6af788dbe482e6a6062685"
+  integrity sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA==
+  dependencies:
+    "@hapi/hoek" "^11.0.2"
+
+"@hapi/bourne@^3.0.0":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@hapi/bourne/-/bourne-3.0.0.tgz#f11fdf7dda62fe8e336fa7c6642d9041f30356d7"
+  integrity sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w==
+
+"@hapi/hoek@^11.0.2":
+  version "11.0.4"
+  resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-11.0.4.tgz#42a7f244fd3dd777792bfb74b8c6340ae9182f37"
+  integrity sha512-PnsP5d4q7289pS2T2EgGz147BFJ2Jpb4yrEdkpz2IhgEUzos1S7HTl7ezWh1yfYzYlj89KzLdCRkqsP6SIryeQ==
+
 "@hapi/hoek@^9.0.0":
   version "9.3.0"
   resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.3.0.tgz#8368869dcb735be2e7f5cb7647de78e167a251fb"
@@ -9453,10 +9559,10 @@ better-opn@~3.0.2:
   dependencies:
     open "^8.0.4"
 
-better-sqlite3@^9.4.0:
-  version "9.4.5"
-  resolved "https://registry.yarnpkg.com/better-sqlite3/-/better-sqlite3-9.4.5.tgz#1d3422443a9924637cb06cc3ccc941b2ae932c65"
-  integrity sha512-uFVyoyZR9BNcjSca+cp3MWCv6upAv+tbMC4SWM51NIMhoQOm4tjIkyxFO/ZsYdGAF61WJBgdzyJcz4OokJi0gQ==
+better-sqlite3@^10.0.0:
+  version "10.1.0"
+  resolved "https://registry.yarnpkg.com/better-sqlite3/-/better-sqlite3-10.1.0.tgz#8dc07e496fc014a7cd2211f79e591f6ba92838e8"
+  integrity sha512-hqpHJaCfKEZFaAWdMh6crdzRWyzQzfP6Ih8TYI0vFn01a6ZTDSbJIMXN+6AMBaBOh99DzUy8l3PsV9R3qnJDng==
   dependencies:
     bindings "^1.5.0"
     prebuild-install "^7.1.1"
@@ -10305,6 +10411,11 @@ cookie@0.5.0:
   resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b"
   integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==
 
+cookie@^0.6.0:
+  version "0.6.0"
+  resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051"
+  integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==
+
 copy-webpack-plugin@^10.2.0:
   version "10.2.4"
   resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-10.2.4.tgz#6c854be3fdaae22025da34b9112ccf81c63308fe"
@@ -13779,6 +13890,11 @@ ip-regex@^2.1.0:
   resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9"
   integrity sha512-58yWmlHpp7VYfcdTwMTvwMmqx/Elfxjd9RXTDyMsbL7lLWmhMylLEqiYVLKuLzOZqVgiWXD9MfR62Vv89VRxkw==
 
+ip3country@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/ip3country/-/ip3country-5.0.0.tgz#f1394b050c51ba9c10cc691c8eb240bba3d7177a"
+  integrity sha512-lcFLMFU4eO1Z7tIpbVFZkaZ5ltqpeaRx7L9NsAbA9uA7/O/rj3RF8+evE5gDitooaTTIqjdzZrenFO/OOxQ2ew==
+
 ipaddr.js@1.9.1, ipaddr.js@^1.9.0:
   version "1.9.1"
   resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3"
@@ -13789,6 +13905,11 @@ ipaddr.js@^2.0.1:
   resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.1.0.tgz#2119bc447ff8c257753b196fc5f1ce08a4cdf39f"
   integrity sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ==
 
+ipaddr.js@^2.1.0:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.2.0.tgz#d33fa7bac284f4de7af949638c9d68157c6b92e8"
+  integrity sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==
+
 is-arguments@^1.0.4:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b"
@@ -15351,6 +15472,11 @@ jose@^5.0.1:
   resolved "https://registry.yarnpkg.com/jose/-/jose-5.1.3.tgz#303959d85c51b5cb14725f930270b72be56abdca"
   integrity sha512-GPExOkcMsCLBTi1YetY2LmkoY559fss0+0KVa6kOfb2YFe84nAM7Nm/XzuZozah4iHgmBGrCOHL5/cy670SBRw==
 
+jose@^5.2.0:
+  version "5.6.3"
+  resolved "https://registry.yarnpkg.com/jose/-/jose-5.6.3.tgz#415688bc84875461c86dfe271ea6029112a23e27"
+  integrity sha512-1Jh//hEEwMhNYPDDLwXHa2ePWgWiFNNUadVmguAAw2IJ6sj9mNxV5tGXJNqlMkJAybF6Lgw1mISDxTePP/187g==
+
 js-base64@^3.7.2:
   version "3.7.5"
   resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-3.7.5.tgz#21e24cf6b886f76d6f5f165bfcd69cc55b9e3fca"
@@ -15603,6 +15729,13 @@ key-encoder@^2.0.3:
     bn.js "^4.11.8"
     elliptic "^6.4.1"
 
+keygrip@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/keygrip/-/keygrip-1.1.0.tgz#871b1681d5e159c62a445b0c74b615e0917e7226"
+  integrity sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==
+  dependencies:
+    tsscmp "1.0.6"
+
 kind-of@^6.0.2:
   version "6.0.3"
   resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd"
@@ -16776,6 +16909,13 @@ node-fetch@^2.2.0, node-fetch@^2.6.0, node-fetch@^2.6.1, node-fetch@^2.6.12, nod
   dependencies:
     whatwg-url "^5.0.0"
 
+node-fetch@^2.6.13:
+  version "2.7.0"
+  resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d"
+  integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==
+  dependencies:
+    whatwg-url "^5.0.0"
+
 node-forge@^1, node-forge@^1.2.1, node-forge@^1.3.1:
   version "1.3.1"
   resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3"
@@ -17024,6 +17164,11 @@ obuf@^1.0.0, obuf@^1.1.2:
   resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e"
   integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==
 
+oidc-token-hash@^5.0.3:
+  version "5.0.3"
+  resolved "https://registry.yarnpkg.com/oidc-token-hash/-/oidc-token-hash-5.0.3.tgz#9a229f0a1ce9d4fc89bcaee5478c97a889e7b7b6"
+  integrity sha512-IF4PcGgzAr6XXSff26Sk/+P4KZFJVuHAJZj3wgO3vX2bMdNVp/QXTP3P7CEm9V1IdG8lDLY3HhiqpsE/nOwpPw==
+
 on-exit-leak-free@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/on-exit-leak-free/-/on-exit-leak-free-2.1.0.tgz#5c703c968f7e7f851885f6459bf8a8a57edc9cc4"
@@ -17567,18 +17712,18 @@ pinkie@^2.0.0:
   resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870"
   integrity sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==
 
-pino-abstract-transport@v1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/pino-abstract-transport/-/pino-abstract-transport-1.0.0.tgz#cc0d6955fffcadb91b7b49ef220a6cc111d48bb3"
-  integrity sha512-c7vo5OpW4wIS42hUVcT5REsL8ZljsUfBjqV/e2sFxmFEFZiq1XLUp5EYLtuDH6PEHq9W1egWqRbnLUP5FuZmOA==
+pino-abstract-transport@^1.2.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/pino-abstract-transport/-/pino-abstract-transport-1.2.0.tgz#97f9f2631931e242da531b5c66d3079c12c9d1b5"
+  integrity sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q==
   dependencies:
     readable-stream "^4.0.0"
     split2 "^4.0.0"
 
-pino-abstract-transport@v1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/pino-abstract-transport/-/pino-abstract-transport-1.1.0.tgz#083d98f966262164504afb989bccd05f665937a8"
-  integrity sha512-lsleG3/2a/JIWUtf9Q5gUNErBqwIu1tUKTT3dUzaf5DySw9ra1wcqKjJjLX1VTY64Wk1eEOYsVGSaGfCK85ekA==
+pino-abstract-transport@v1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/pino-abstract-transport/-/pino-abstract-transport-1.0.0.tgz#cc0d6955fffcadb91b7b49ef220a6cc111d48bb3"
+  integrity sha512-c7vo5OpW4wIS42hUVcT5REsL8ZljsUfBjqV/e2sFxmFEFZiq1XLUp5EYLtuDH6PEHq9W1egWqRbnLUP5FuZmOA==
   dependencies:
     readable-stream "^4.0.0"
     split2 "^4.0.0"
@@ -17615,22 +17760,22 @@ pino@^8.0.0, pino@^8.11.0, pino@^8.6.1:
     sonic-boom "^3.1.0"
     thread-stream "^2.0.0"
 
-pino@^8.15.0:
-  version "8.15.1"
-  resolved "https://registry.yarnpkg.com/pino/-/pino-8.15.1.tgz#04b815ff7aa4e46b1bbab88d8010aaa2b17eaba4"
-  integrity sha512-Cp4QzUQrvWCRJaQ8Lzv0mJzXVk4z2jlq8JNKMGaixC2Pz5L4l2p95TkuRvYbrEbe85NQsDKrAd4zalf7Ml6WiA==
+pino@^8.21.0:
+  version "8.21.0"
+  resolved "https://registry.yarnpkg.com/pino/-/pino-8.21.0.tgz#e1207f3675a2722940d62da79a7a55a98409f00d"
+  integrity sha512-ip4qdzjkAyDDZklUaZkcRFb2iA118H9SgRh8yzTkSQK8HilsOJF7rSY8HoW5+I0M46AZgX/pxbprf2vvzQCE0Q==
   dependencies:
     atomic-sleep "^1.0.0"
     fast-redact "^3.1.1"
     on-exit-leak-free "^2.1.0"
-    pino-abstract-transport v1.1.0
+    pino-abstract-transport "^1.2.0"
     pino-std-serializers "^6.0.0"
-    process-warning "^2.0.0"
+    process-warning "^3.0.0"
     quick-format-unescaped "^4.0.3"
     real-require "^0.2.0"
     safe-stable-stringify "^2.3.1"
-    sonic-boom "^3.1.0"
-    thread-stream "^2.0.0"
+    sonic-boom "^3.7.0"
+    thread-stream "^2.6.0"
 
 pirates@^4.0.1, pirates@^4.0.4, pirates@^4.0.5:
   version "4.0.6"
@@ -18392,6 +18537,11 @@ process-warning@^2.0.0:
   resolved "https://registry.yarnpkg.com/process-warning/-/process-warning-2.2.0.tgz#008ec76b579820a8e5c35d81960525ca64feb626"
   integrity sha512-/1WZ8+VQjR6avWOgHeEPd7SDQmFQ1B5mC1eRXsCm5TarlNmx/wCsa5GEaxGm05BORRtyG/Ex/3xq3TuRvq57qg==
 
+process-warning@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/process-warning/-/process-warning-3.0.0.tgz#96e5b88884187a1dce6f5c3166d611132058710b"
+  integrity sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==
+
 process@^0.11.10:
   version "0.11.10"
   resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
@@ -20229,6 +20379,13 @@ sonic-boom@^3.1.0:
   dependencies:
     atomic-sleep "^1.0.0"
 
+sonic-boom@^3.7.0:
+  version "3.8.1"
+  resolved "https://registry.yarnpkg.com/sonic-boom/-/sonic-boom-3.8.1.tgz#d5ba8c4e26d6176c9a1d14d549d9ff579a163422"
+  integrity sha512-y4Z8LCDBuum+PBP3lSV7RHrXscqksve/bi0as7mhwVnBW+/wUqKT/2Kb7um8yqcFy0duYbbPxzt89Zy2nOCaxg==
+  dependencies:
+    atomic-sleep "^1.0.0"
+
 source-list-map@^2.0.0, source-list-map@^2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34"
@@ -20419,6 +20576,16 @@ statsig-js@4.45.1:
     js-sha256 "^0.10.1"
     uuid "^8.3.2"
 
+statsig-node@^5.23.1:
+  version "5.25.1"
+  resolved "https://registry.yarnpkg.com/statsig-node/-/statsig-node-5.25.1.tgz#6d8ea9ecaad6c09250e5ff7d33eda9fd0f9c05f4"
+  integrity sha512-K8+1psxFVdFr5LyXwDotJqBl7uKt8vbZO2e/9zzbLI4yDOuLDoItG5Ju5QAR0oUfEdEAANOzwV2yA052Wrc/Xw==
+  dependencies:
+    ip3country "^5.0.0"
+    node-fetch "^2.6.13"
+    ua-parser-js "^1.0.2"
+    uuid "^8.3.2"
+
 statsig-react-native-expo@^4.6.1:
   version "4.6.1"
   resolved "https://registry.yarnpkg.com/statsig-react-native-expo/-/statsig-react-native-expo-4.6.1.tgz#0bdf49fee7112f7f28bff2405f4ba0c1727bb3d6"
@@ -21052,6 +21219,13 @@ thread-stream@^2.0.0:
   dependencies:
     real-require "^0.2.0"
 
+thread-stream@^2.6.0:
+  version "2.7.0"
+  resolved "https://registry.yarnpkg.com/thread-stream/-/thread-stream-2.7.0.tgz#d8a8e1b3fd538a6cca8ce69dbe5d3d097b601e11"
+  integrity sha512-qQiRWsU/wvNolI6tbbCKd9iKaTnCXsTwVxhhKM6nctPdujTyztjlbUkUTUymidWcMnZ5pWR0ej4a0tjsW021vw==
+  dependencies:
+    real-require "^0.2.0"
+
 throat@^5.0.0:
   version "5.0.0"
   resolved "https://registry.yarnpkg.com/throat/-/throat-5.0.0.tgz#c5199235803aad18754a667d659b5e72ce16764b"
@@ -21251,6 +21425,11 @@ tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.1, tslib@^2.4
   resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae"
   integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==
 
+tsscmp@1.0.6:
+  version "1.0.6"
+  resolved "https://registry.yarnpkg.com/tsscmp/-/tsscmp-1.0.6.tgz#85b99583ac3589ec4bfef825b5000aa911d605eb"
+  integrity sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==
+
 tsutils@^3.21.0:
   version "3.21.0"
   resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623"
@@ -21388,6 +21567,11 @@ ua-parser-js@^0.7.33:
   resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.35.tgz#8bda4827be4f0b1dda91699a29499575a1f1d307"
   integrity sha512-veRf7dawaj9xaWEu9HoTVn5Pggtc/qj+kqTOFvNiN1l0YdxwC1kvel57UCjThjGa3BHBihE8/UJAHI+uQHmd/g==
 
+ua-parser-js@^1.0.2:
+  version "1.0.38"
+  resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-1.0.38.tgz#66bb0c4c0e322fe48edfe6d446df6042e62f25e2"
+  integrity sha512-Aq5ppTOfvrCMgAPneW1HfWj66Xi7XL+/mIy996R1/CLS/rcyJQm6QZdsKrUeivDFQ+Oc9Wyuwor8Ze8peEoUoQ==
+
 ua-parser-js@^1.0.35:
   version "1.0.35"
   resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-1.0.35.tgz#c4ef44343bc3db0a3cbefdf21822f1b1fc1ab011"
@@ -21432,6 +21616,11 @@ undici@^5.28.2:
   dependencies:
     "@fastify/busboy" "^2.0.0"
 
+undici@^6.14.1:
+  version "6.19.5"
+  resolved "https://registry.yarnpkg.com/undici/-/undici-6.19.5.tgz#5829101361b583b53206e81579f4df71c56d6be8"
+  integrity sha512-LryC15SWzqQsREHIOUybavaIHF5IoL0dJ9aWWxL/PgT1KfqAW5225FZpDUFlt9xiDMS2/S7DOKhFWA7RLksWdg==
+
 unfetch@^3.1.1:
   version "3.1.2"
   resolved "https://registry.yarnpkg.com/unfetch/-/unfetch-3.1.2.tgz#dc271ef77a2800768f7b459673c5604b5101ef77"
@@ -22603,7 +22792,7 @@ zod-validation-error@^3.0.3:
   resolved "https://registry.yarnpkg.com/zod-validation-error/-/zod-validation-error-3.3.0.tgz#2cfe81b62d044e0453d1aa3ae7c32a2f36dde9af"
   integrity sha512-Syib9oumw1NTqEv4LT0e6U83Td9aVRk9iTXPUQr1otyV1PuXQKOvOwhMNqZIq5hluzHP2pMgnOmHEo7kPdI2mw==
 
-zod@3.23.8, zod@^3.14.2, zod@^3.20.2, zod@^3.21.4, zod@^3.22.4:
+zod@3.23.8, zod@^3.14.2, zod@^3.20.2, zod@^3.21.4, zod@^3.22.4, zod@^3.23.8:
   version "3.23.8"
   resolved "https://registry.yarnpkg.com/zod/-/zod-3.23.8.tgz#e37b957b5d52079769fb8097099b592f0ef4067d"
   integrity sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==