diff --git a/.changeset/brave-countries-return.md b/.changeset/brave-countries-return.md
new file mode 100644
index 000000000..4ce768d17
--- /dev/null
+++ b/.changeset/brave-countries-return.md
@@ -0,0 +1,5 @@
+---
+"@atproto/oauth-provider": patch
+---
+
+Add support for password reset
diff --git a/.changeset/eleven-ducks-boil.md b/.changeset/eleven-ducks-boil.md
new file mode 100644
index 000000000..cdd84f0ba
--- /dev/null
+++ b/.changeset/eleven-ducks-boil.md
@@ -0,0 +1,5 @@
+---
+"@atproto-labs/fetch": patch
+---
+
+Improved error response parsing
diff --git a/.changeset/hip-feet-play.md b/.changeset/hip-feet-play.md
new file mode 100644
index 000000000..a0ba6e08f
--- /dev/null
+++ b/.changeset/hip-feet-play.md
@@ -0,0 +1,5 @@
+---
+"@atproto/oauth-provider": minor
+---
+
+Add support for account sign-up
diff --git a/.changeset/hungry-buttons-clean.md b/.changeset/hungry-buttons-clean.md
new file mode 100644
index 000000000..21869c2c6
--- /dev/null
+++ b/.changeset/hungry-buttons-clean.md
@@ -0,0 +1,5 @@
+---
+"@atproto/oauth-client-browser-example": patch
+---
+
+Update react to version 19
diff --git a/.changeset/long-bats-guess.md b/.changeset/long-bats-guess.md
new file mode 100644
index 000000000..80a0de911
--- /dev/null
+++ b/.changeset/long-bats-guess.md
@@ -0,0 +1,7 @@
+---
+"@atproto/oauth-provider": patch
+"@atproto/oauth-types": patch
+"@atproto/jwk": patch
+---
+
+Properly support locales with 3 chars (Asturian)
diff --git a/.changeset/rotten-hornets-develop.md b/.changeset/rotten-hornets-develop.md
new file mode 100644
index 000000000..54d87b47b
--- /dev/null
+++ b/.changeset/rotten-hornets-develop.md
@@ -0,0 +1,5 @@
+---
+"@atproto/oauth-provider": patch
+---
+
+Add support for multiple locales
diff --git a/.changeset/short-masks-punch.md b/.changeset/short-masks-punch.md
new file mode 100644
index 000000000..950ecb518
--- /dev/null
+++ b/.changeset/short-masks-punch.md
@@ -0,0 +1,5 @@
+---
+"@atproto/syntax": patch
+---
+
+Deprecate unused classes
diff --git a/.changeset/sour-guests-work.md b/.changeset/sour-guests-work.md
new file mode 100644
index 000000000..553a69de0
--- /dev/null
+++ b/.changeset/sour-guests-work.md
@@ -0,0 +1,5 @@
+---
+"@atproto-labs/rollup-plugin-bundle-manifest": patch
+---
+
+Improve typing of plugin
diff --git a/.changeset/tall-rules-hammer.md b/.changeset/tall-rules-hammer.md
new file mode 100644
index 000000000..9f70720c7
--- /dev/null
+++ b/.changeset/tall-rules-hammer.md
@@ -0,0 +1,5 @@
+---
+"@atproto/oauth-client": patch
+---
+
+Minor code optimizations
diff --git a/.changeset/tiny-goats-sing.md b/.changeset/tiny-goats-sing.md
new file mode 100644
index 000000000..3fab228e9
--- /dev/null
+++ b/.changeset/tiny-goats-sing.md
@@ -0,0 +1,5 @@
+---
+"@atproto-labs/fetch": patch
+---
+
+Remove explicit dependency on "zod". Improved typing of `fetchJsonZodProcessor` function.
diff --git a/.changeset/weak-elephants-thank.md b/.changeset/weak-elephants-thank.md
new file mode 100644
index 000000000..4305cfdc3
--- /dev/null
+++ b/.changeset/weak-elephants-thank.md
@@ -0,0 +1,5 @@
+---
+"@atproto/oauth-client-browser-example": patch
+---
+
+Build using SWC
diff --git a/.changeset/young-parents-learn.md b/.changeset/young-parents-learn.md
new file mode 100644
index 000000000..6acefadeb
--- /dev/null
+++ b/.changeset/young-parents-learn.md
@@ -0,0 +1,5 @@
+---
+"@atproto/pds": patch
+---
+
+Add support for account sign-ups during OAuth flows
diff --git a/.github/workflows/repo.yaml b/.github/workflows/repo.yaml
index e8db4b20b..de2e2d01d 100644
--- a/.github/workflows/repo.yaml
+++ b/.github/workflows/repo.yaml
@@ -35,6 +35,7 @@ jobs:
path: |
packages/*/dist
packages/*/*/dist
+ packages/oauth/oauth-provider/src/assets/app/locales/*/messages.ts
retention-days: 1
test:
name: Test
diff --git a/.npmrc b/.npmrc
index 8e012302a..287c23804 100644
--- a/.npmrc
+++ b/.npmrc
@@ -1 +1,2 @@
enable-pre-post-scripts = true
+include-workspace-root = true
diff --git a/.prettierignore b/.prettierignore
index e6453c5df..570ee8a72 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -12,3 +12,6 @@ packages/api/src/client
packages/bsky/src/lexicon
packages/pds/src/lexicon
packages/ozone/src/lexicon
+
+# Automatically generated by lingui
+packages/oauth/oauth-provider/src/assets/app/locales/*/messages.ts
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 4f8e93ded..2d95c43f7 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -12,6 +12,7 @@
"consolas",
"dpop",
"googleusercontent",
+ "hcaptcha",
"hexeditor",
"ingester",
"insertable",
diff --git a/package.json b/package.json
index 2601cf849..a97bf952d 100644
--- a/package.json
+++ b/package.json
@@ -11,7 +11,7 @@
"packageManager": "pnpm@8.15.9",
"scripts": {
"lint:fix": "pnpm lint --fix",
- "lint": "eslint . --ext .ts,.js",
+ "lint": "eslint . --ext .ts,.js,.tsx,.jsx",
"style:fix": "prettier --write .",
"style": "prettier --check .",
"verify": "pnpm --stream '/^verify:.+$/'",
@@ -19,13 +19,13 @@
"verify:lint": "pnpm lint",
"verify:types": "tsc --build tsconfig.json",
"format": "pnpm lint:fix && pnpm style:fix",
- "codegen": "pnpm run --recursive --stream --filter '@atproto/lex-cli...' build --force && pnpm run --recursive --stream --parallel codegen",
- "build": "pnpm --recursive --stream build",
- "dev": "NODE_ENV=development pnpm --stream '/^dev:.+$/'",
- "dev:tsc": "tsc --build tsconfig.json --watch",
- "dev:pkg": "pnpm --recursive --parallel --stream dev",
- "test": "LOG_ENABLED=false ./packages/dev-infra/with-test-redis-and-db.sh pnpm --stream -r test",
- "test:withFlags": "LOG_ENABLED=false ./packages/dev-infra/with-test-redis-and-db.sh pnpm --stream -r test --",
+ "precodegen": "pnpm run --recursive --stream --filter '@atproto/lex-cli...' build --force",
+ "codegen": "pnpm run --recursive --stream --parallel codegen",
+ "build": "pnpm run --recursive --stream '/^(build|build:.+)$/'",
+ "dev": "NODE_ENV=development pnpm run --recursive --parallel --stream '/^(dev|dev:.+)$/'",
+ "dev:tsc": "tsc --build tsconfig.json --preserveWatchOutput --watch",
+ "test": "LOG_ENABLED=false ./packages/dev-infra/with-test-redis-and-db.sh pnpm test --stream --recursive",
+ "test:withFlags": "pnpm run test --",
"changeset": "changeset",
"release": "pnpm build && changeset publish",
"version-packages": "changeset version && git add ."
diff --git a/packages/dev-env/src/pds.ts b/packages/dev-env/src/pds.ts
index d721a8797..5c2f7bd7e 100644
--- a/packages/dev-env/src/pds.ts
+++ b/packages/dev-env/src/pds.ts
@@ -37,7 +37,9 @@ export class TestPds {
recoveryDidKey: recoveryKey,
adminPassword: ADMIN_PASSWORD,
jwtSecret: JWT_SECRET,
- serviceHandleDomains: ['.test'],
+ // @NOTE ".example" will not actually work and is only used to display
+ // multiple domains in the sing-up UI
+ serviceHandleDomains: ['.test', '.example'],
bskyAppViewUrl: 'https://appview.invalid',
bskyAppViewDid: 'did:example:invalid',
bskyAppViewCdnUrlPattern: 'http://cdn.appview.com/%s/%s/%s',
@@ -47,10 +49,15 @@ export class TestPds {
inviteRequired: false,
disableSsrfProtection: true,
serviceName: 'Development PDS',
- brandColor: '#ffcb1e',
- errorColor: undefined,
+ brandColor: '#8338ec',
+ errorColor: '#ff006e',
+ warningColor: '#fb5607',
+ successColor: '#02c39a',
logoUrl:
- 'https://uxwing.com/wp-content/themes/uxwing/download/animals-and-birds/bee-icon.png',
+ // Using a "data:" instead of a real URL to avoid making CORS requests in dev.
+ // License: https://uxwing.com/license/
+ // Source: https://uxwing.com/bee-icon/
+ `data:image/svg+xml;base64,${Buffer.from('', 'utf8').toString('base64')}`,
homeUrl: 'https://bsky.social/',
termsOfServiceUrl: 'https://bsky.social/about/support/tos',
privacyPolicyUrl: 'https://bsky.social/about/support/privacy-policy',
diff --git a/packages/internal/fetch/package.json b/packages/internal/fetch/package.json
index 4c5b913a3..fbf6beb05 100644
--- a/packages/internal/fetch/package.json
+++ b/packages/internal/fetch/package.json
@@ -28,9 +28,6 @@
"devDependencies": {
"typescript": "^5.6.3"
},
- "optionalDependencies": {
- "zod": "^3.23.8"
- },
"scripts": {
"build": "tsc --build tsconfig.json"
}
diff --git a/packages/internal/fetch/src/fetch-response.ts b/packages/internal/fetch/src/fetch-response.ts
index 09e903bfe..d483fc819 100644
--- a/packages/internal/fetch/src/fetch-response.ts
+++ b/packages/internal/fetch/src/fetch-response.ts
@@ -1,4 +1,3 @@
-import type { ParseParams, TypeOf, ZodTypeAny } from 'zod'
import { Transformer, pipe } from '@atproto-labs/pipe'
import { FetchError } from './fetch-error.js'
import { TransformedResponse } from './transformed-response.js'
@@ -6,7 +5,6 @@ import {
Json,
MaxBytesTransformStream,
cancelBody,
- ifObject,
ifString,
logCancellationError,
} from './util.js'
@@ -69,15 +67,16 @@ const extractResponseMessage: ResponseMessageGetter = async (response) => {
const json: unknown = await response.json()
if (typeof json === 'string') return json
+ if (typeof json === 'object' && json != null) {
+ const errorDescription = ifString(json['error_description'])
+ if (errorDescription) return errorDescription
- const errorDescription = ifString(ifObject(json)?.['error_description'])
- if (errorDescription) return errorDescription
+ const error = ifString(json['error'])
+ if (error) return error
- const error = ifString(ifObject(json)?.['error'])
- if (error) return error
-
- const message = ifString(ifObject(json)?.['message'])
- if (message) return message
+ const message = ifString(json['message'])
+ if (message) return message
+ }
}
} catch {
// noop
@@ -283,10 +282,31 @@ export function fetchJsonProcessor(
)
}
-export function fetchJsonZodProcessor(
- schema: S,
- params?: Partial,
-): Transformer> {
- return async (jsonResponse: ParsedJsonResponse): Promise> =>
- schema.parseAsync(jsonResponse.json, params)
+export type SyncValidationSchema = {
+ parse(value: unknown, params?: P): S
}
+
+export type AsyncValidationSchema = {
+ parseAsync(value: unknown, params?: P): Promise
+}
+
+export function fetchJsonValidatorProcessor(
+ schema: SyncValidationSchema | AsyncValidationSchema,
+ params?: P,
+): Transformer {
+ if ('parseAsync' in schema && typeof schema.parseAsync === 'function') {
+ return async (jsonResponse: ParsedJsonResponse): Promise =>
+ schema.parseAsync(jsonResponse.json, params)
+ }
+
+ if ('parse' in schema && typeof schema.parse === 'function') {
+ return async (jsonResponse: ParsedJsonResponse): Promise =>
+ schema.parse(jsonResponse.json, params)
+ }
+
+ // Needed for type safety (and allows fool proofing the usage of this function)
+ throw new TypeError('Invalid schema')
+}
+
+/** @note Use {@link fetchJsonValidatorProcessor} instead */
+export const fetchJsonZodProcessor = fetchJsonValidatorProcessor
diff --git a/packages/internal/fetch/src/util.ts b/packages/internal/fetch/src/util.ts
index 67d5a81e2..00b64d0e3 100644
--- a/packages/internal/fetch/src/util.ts
+++ b/packages/internal/fetch/src/util.ts
@@ -24,24 +24,6 @@ export function isIp(hostname: string) {
return false
}
-const plainObjectProto = Object.prototype
-export const ifObject = (v: V) => {
- if (typeof v === 'object' && v != null && !Array.isArray(v)) {
- const proto = Object.getPrototypeOf(v)
- if (proto === null || proto === plainObjectProto) {
- // eslint-disable-next-line @typescript-eslint/ban-types
- return v as V extends JsonScalar | JsonArray | Function | symbol
- ? never
- : V extends Json
- ? V
- : // Plain object are (mostly) safe to access using a string index
- Record
- }
- }
-
- return undefined
-}
-
export const ifString = (v: V) => (typeof v === 'string' ? v : undefined)
export class MaxBytesTransformStream extends TransformStream<
diff --git a/packages/internal/rollup-plugin-bundle-manifest/src/index.ts b/packages/internal/rollup-plugin-bundle-manifest/src/index.ts
index dae083970..8c5623e0d 100644
--- a/packages/internal/rollup-plugin-bundle-manifest/src/index.ts
+++ b/packages/internal/rollup-plugin-bundle-manifest/src/index.ts
@@ -32,7 +32,7 @@ export default function bundleManifest({
}: {
name?: string
data?: boolean
-} = {}): Plugin {
+} = {}): Plugin {
return {
name: 'bundle-manifest',
generateBundle(outputOptions, bundle) {
diff --git a/packages/oauth/jwk/src/jwt.ts b/packages/oauth/jwk/src/jwt.ts
index 75a84557f..264857fd3 100644
--- a/packages/oauth/jwk/src/jwt.ts
+++ b/packages/oauth/jwk/src/jwt.ts
@@ -129,7 +129,7 @@ export const jwtPayloadSchema = z
.optional(),
locale: z
.string()
- .regex(/^[a-z]{2}(-[A-Z]{2})?$/)
+ .regex(/^[a-z]{2,3}(-[A-Z]{2})?$/)
.optional(),
updated_at: z.number().int().optional(),
diff --git a/packages/oauth/oauth-client-browser-example/package.json b/packages/oauth/oauth-client-browser-example/package.json
index ba383a705..d086ebfb0 100644
--- a/packages/oauth/oauth-client-browser-example/package.json
+++ b/packages/oauth/oauth-client-browser-example/package.json
@@ -38,15 +38,14 @@
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-html": "^1.0.4",
"@rollup/plugin-node-resolve": "^15.2.3",
- "@rollup/plugin-replace": "^5.0.5",
- "@rollup/plugin-terser": "^0.4.4",
- "@rollup/plugin-typescript": "^11.1.6",
- "@types/react": "^18.2.50",
- "@types/react-dom": "^18.2.18",
+ "@rollup/plugin-swc": "^0.4.0",
+ "@swc/helpers": "^0.5.15",
+ "@types/react": "^19.0.10",
+ "@types/react-dom": "^19.0.4",
"autoprefixer": "^10.4.17",
"postcss": "^8.4.33",
- "react": "^18.2.0",
- "react-dom": "^18.2.0",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0",
"rollup": "^4.13.0",
"rollup-plugin-postcss": "^4.0.2",
"rollup-plugin-serve": "^1.1.1",
diff --git a/packages/oauth/oauth-client-browser-example/rollup.config.js b/packages/oauth/oauth-client-browser-example/rollup.config.js
index 73f561c3d..b5b79e589 100644
--- a/packages/oauth/oauth-client-browser-example/rollup.config.js
+++ b/packages/oauth/oauth-client-browser-example/rollup.config.js
@@ -4,9 +4,7 @@ const { default: commonjs } = require('@rollup/plugin-commonjs')
const { default: html, makeHtmlAttributes } = require('@rollup/plugin-html')
const { default: json } = require('@rollup/plugin-json')
const { default: nodeResolve } = require('@rollup/plugin-node-resolve')
-const { default: replace } = require('@rollup/plugin-replace')
-const { default: terser } = require('@rollup/plugin-terser')
-const { default: typescript } = require('@rollup/plugin-typescript')
+const { default: swc } = require('@rollup/plugin-swc')
const { defineConfig } = require('rollup')
const {
default: manifest,
@@ -19,7 +17,7 @@ module.exports = defineConfig((commandLineArguments) => {
process.env['NODE_ENV'] ??
(commandLineArguments.watch ? 'development' : 'production')
- const minify = NODE_ENV !== 'development'
+ const devMode = NODE_ENV === 'development'
return {
input: 'src/main.tsx',
@@ -30,17 +28,46 @@ module.exports = defineConfig((commandLineArguments) => {
format: 'iife',
},
plugins: [
+ {
+ name: 'resolve-swc-helpers',
+ resolveId(src) {
+ // For some reason, "nodeResolve" doesn't resolve these:
+ if (src.startsWith('@swc/helpers/')) return require.resolve(src)
+ },
+ },
nodeResolve({ preferBuiltins: false, browser: true }),
commonjs(),
json(),
postcss({ config: true, extract: true, minimize: false }),
- typescript({
- tsconfig: './tsconfig.build.json',
- outputToFilesystem: true,
- }),
- replace({
- preventAssignment: true,
- values: { 'process.env.NODE_ENV': JSON.stringify(NODE_ENV) },
+ swc({
+ swc: {
+ swcrc: false,
+ configFile: false,
+ sourceMaps: true,
+ minify: !devMode,
+ jsc: {
+ minify: {
+ compress: {
+ module: true,
+ unused: true,
+ },
+ mangle: true,
+ },
+ externalHelpers: true,
+ target: 'es2020',
+ parser: { syntax: 'typescript', tsx: true },
+ transform: {
+ useDefineForClassFields: true,
+ react: { runtime: 'automatic' },
+ optimizer: {
+ simplify: true,
+ globals: {
+ vars: { 'process.env.NODE_ENV': JSON.stringify(NODE_ENV) },
+ },
+ },
+ },
+ },
+ },
}),
html({
title: 'OAuth Client Example',
@@ -79,7 +106,6 @@ module.exports = defineConfig((commandLineArguments) => {