diff --git a/apps/api/package.json b/apps/api/package.json index 4c3eba61..5e975722 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -34,10 +34,10 @@ "@atproto/api": "^0.13.31", "@atproto/common": "^0.4.6", "@atproto/identity": "^0.4.5", - "@atproto/jwk-jose": "0.1.5", + "@atproto/jwk-jose": "0.1.11", "@atproto/lex-cli": "^0.5.6", "@atproto/lexicon": "^0.4.5", - "@atproto/oauth-client-node": "0.2.14", + "@atproto/oauth-client-node": "0.3.16", "@atproto/sync": "^0.1.11", "@atproto/syntax": "^0.3.1", "@atproto/xrpc-server": "^0.7.8", diff --git a/apps/api/src/auth/client.ts b/apps/api/src/auth/client.ts index 219a15a5..6c3db097 100644 --- a/apps/api/src/auth/client.ts +++ b/apps/api/src/auth/client.ts @@ -1,117 +1,26 @@ import { JoseKey } from "@atproto/jwk-jose"; -import { - AuthorizeOptions, - NodeOAuthClient, - NodeOAuthClientOptions, - OAuthAuthorizationRequestParameters, - type RuntimeLock, -} from "@atproto/oauth-client-node"; +import { type RuntimeLock } from "@atproto/oauth-client-node"; import Redis from "ioredis"; import Redlock from "redlock"; import type { Database } from "../db"; import { env } from "../lib/env"; import { SessionStore, StateStore } from "./storage"; - -export const FALLBACK_ALG = "ES256"; - -export class CustomOAuthClient extends NodeOAuthClient { - constructor(options: NodeOAuthClientOptions) { - super(options); - } - - async authorize( - input: string, - { signal, ...options }: AuthorizeOptions = {}, - ): Promise { - const redirectUri = - options?.redirect_uri ?? this.clientMetadata.redirect_uris[0]; - if (!this.clientMetadata.redirect_uris.includes(redirectUri)) { - // The server will enforce this, but let's catch it early - throw new TypeError("Invalid redirect_uri"); - } - - const { identity, metadata } = await this.oauthResolver.resolve(input, { - signal, - }); - - const pkce = await this.runtime.generatePKCE(); - const dpopKey = await this.runtime.generateKey( - metadata.dpop_signing_alg_values_supported || [FALLBACK_ALG], - ); - - const state = await this.runtime.generateNonce(); - - await this.stateStore.set(state, { - iss: metadata.issuer, - dpopKey, - verifier: pkce.verifier, - appState: options?.state, - }); - - const parameters: OAuthAuthorizationRequestParameters = { - ...options, - - client_id: this.clientMetadata.client_id, - redirect_uri: redirectUri, - code_challenge: pkce.challenge, - code_challenge_method: pkce.method, - state, - login_hint: identity && !options.prompt ? input : undefined, - response_mode: this.responseMode, - response_type: "code" as const, - scope: options?.scope ?? this.clientMetadata.scope, - }; - - const authorizationUrl = new URL(metadata.authorization_endpoint); - - // Since the user will be redirected to the authorization_endpoint url using - // a browser, we need to make sure that the url is valid. - if ( - authorizationUrl.protocol !== "https:" && - authorizationUrl.protocol !== "http:" - ) { - throw new TypeError( - `Invalid authorization endpoint protocol: ${authorizationUrl.protocol}`, - ); - } - - if (metadata.pushed_authorization_request_endpoint) { - const server = await this.serverFactory.fromMetadata(metadata, dpopKey); - const parResponse = await server.request( - "pushed_authorization_request", - parameters, - ); - - authorizationUrl.searchParams.set( - "client_id", - this.clientMetadata.client_id, - ); - authorizationUrl.searchParams.set("request_uri", parResponse.request_uri); - return authorizationUrl; - } else if (metadata.require_pushed_authorization_requests) { - throw new Error( - "Server requires pushed authorization requests (PAR) but no PAR endpoint is available", - ); - } else { - for (const [key, value] of Object.entries(parameters)) { - if (value) authorizationUrl.searchParams.set(key, String(value)); - } - - // Length of the URL that will be sent to the server - const urlLength = - authorizationUrl.pathname.length + authorizationUrl.search.length; - if (urlLength < 2048) { - return authorizationUrl; - } else if (!metadata.pushed_authorization_request_endpoint) { - throw new Error("Login URL too long"); - } - } - - throw new Error( - "Server does not support pushed authorization requests (PAR)", - ); - } -} +import { CustomOAuthClient } from "./oauth-client"; + +export const SCOPES = [ + "atproto", + "repo:app.rocksky.album", + "repo:app.rocksky.artist", + "repo:app.rocksky.graph.follow", + "repo:app.rocksky.like", + "repo:app.rocksky.playlist", + "repo:app.rocksky.scrobble", + "repo:app.rocksky.shout", + "repo:app.rocksky.song", + "repo:app.rocksky.feed.generator", + "repo:fm.teal.alpha.feed.play", + "repo:fm.teal.alpha.actor.status", +]; export const createClient = async (db: Database) => { const publicUrl = env.PUBLIC_URL; @@ -139,10 +48,10 @@ export const createClient = async (db: Database) => { ? `${url}/oauth-client-metadata.json` : `http://localhost?redirect_uri=${enc( `${url}/oauth/callback`, - )}&scope=${enc("atproto transition:generic")}`, + )}&scope=${enc(SCOPES.join(" "))}`, client_uri: url, redirect_uris: [`${url}/oauth/callback`], - scope: "atproto transition:generic", + scope: SCOPES.join(" "), grant_types: ["authorization_code", "refresh_token"], response_types: ["code"], application_type: "web", diff --git a/apps/api/src/auth/oauth-client-auth.ts b/apps/api/src/auth/oauth-client-auth.ts new file mode 100644 index 00000000..27b47b2e --- /dev/null +++ b/apps/api/src/auth/oauth-client-auth.ts @@ -0,0 +1,84 @@ +import { + ClientMetadata, + Keyset, + OAuthAuthorizationServerMetadata, +} from "@atproto/oauth-client-node"; + +import { ClientAuthMethod } from "@atproto/oauth-client/dist/oauth-client-auth"; + +export const FALLBACK_ALG = "ES256"; + +function supportedMethods(serverMetadata: OAuthAuthorizationServerMetadata) { + return serverMetadata["token_endpoint_auth_methods_supported"]; +} + +function supportedAlgs(serverMetadata: OAuthAuthorizationServerMetadata) { + return ( + serverMetadata["token_endpoint_auth_signing_alg_values_supported"] ?? [ + // @NOTE If not specified, assume that the server supports the ES256 + // algorithm, as prescribed by the spec: + // + // > Clients and Authorization Servers currently must support the ES256 + // > cryptographic system [for client authentication]. + // + // https://atproto.com/specs/oauth#confidential-client-authentication + FALLBACK_ALG, + ] + ); +} + +export function negotiateClientAuthMethod( + serverMetadata: OAuthAuthorizationServerMetadata, + clientMetadata: ClientMetadata, + keyset?: Keyset, +): ClientAuthMethod { + const method = clientMetadata.token_endpoint_auth_method; + + // @NOTE ATproto spec requires that AS support both "none" and + // "private_key_jwt", and that clients use one of the other. The following + // check ensures that the AS is indeed compliant with this client's + // configuration. + const methods = supportedMethods(serverMetadata); + if (!methods.includes(method)) { + throw new Error( + `The server does not support "${method}" authentication. Supported methods are: ${methods.join( + ", ", + )}.`, + ); + } + + if (method === "private_key_jwt") { + // Invalid client configuration. This should not happen as + // "validateClientMetadata" already check this. + if (!keyset) throw new Error("A keyset is required for private_key_jwt"); + + const alg = supportedAlgs(serverMetadata); + + // @NOTE we can't use `keyset.findPrivateKey` here because we can't enforce + // that the returned key contains a "kid". The following implementation is + // more robust against keysets containing keys without a "kid" property. + for (const key of keyset.list({ alg, usage: "sign" })) { + // Return the first key from the key set that matches the server's + // supported algorithms. + if (key.kid) return { method: "private_key_jwt", kid: key.kid }; + } + + throw new Error( + alg.includes(FALLBACK_ALG) + ? `Client authentication method "${method}" requires at least one "${FALLBACK_ALG}" signing key with a "kid" property` + : // AS is not compliant with the ATproto OAuth spec. + `Authorization server requires "${method}" authentication method, but does not support "${FALLBACK_ALG}" algorithm.`, + ); + } + + if (method === "none") { + return { method: "none" }; + } + + throw new Error( + `The ATProto OAuth spec requires that client use either "none" or "private_key_jwt" authentication method.` + + (method === "client_secret_basic" + ? ' You might want to explicitly set "token_endpoint_auth_method" to one of those values in the client metadata document.' + : ` You set "${method}" which is not allowed.`), + ); +} diff --git a/apps/api/src/auth/oauth-client.ts b/apps/api/src/auth/oauth-client.ts new file mode 100644 index 00000000..5fbddb62 --- /dev/null +++ b/apps/api/src/auth/oauth-client.ts @@ -0,0 +1,116 @@ +import { + AuthorizeOptions, + NodeOAuthClient, + NodeOAuthClientOptions, + OAuthAuthorizationRequestParameters, +} from "@atproto/oauth-client-node"; +import { FALLBACK_ALG, negotiateClientAuthMethod } from "./oauth-client-auth"; + +export class CustomOAuthClient extends NodeOAuthClient { + constructor(options: NodeOAuthClientOptions) { + super(options); + } + + async authorize( + input: string, + { signal, ...options }: AuthorizeOptions = {}, + ): Promise { + const redirectUri = + options?.redirect_uri ?? this.clientMetadata.redirect_uris[0]; + if (!this.clientMetadata.redirect_uris.includes(redirectUri)) { + // The server will enforce this, but let's catch it early + throw new TypeError("Invalid redirect_uri"); + } + + const { identityInfo, metadata } = await this.oauthResolver.resolve(input, { + signal, + }); + + const pkce = await this.runtime.generatePKCE(); + const dpopKey = await this.runtime.generateKey( + metadata.dpop_signing_alg_values_supported || [FALLBACK_ALG], + ); + + const authMethod = negotiateClientAuthMethod( + metadata, + this.clientMetadata, + this.keyset, + ); + const state = await this.runtime.generateNonce(); + + await this.stateStore.set(state, { + iss: metadata.issuer, + authMethod, + dpopKey, + verifier: pkce.verifier, + appState: options?.state, + }); + + const parameters: OAuthAuthorizationRequestParameters = { + ...options, + + client_id: this.clientMetadata.client_id, + redirect_uri: redirectUri, + code_challenge: pkce.challenge, + code_challenge_method: pkce.method, + state, + login_hint: identityInfo && !options.prompt ? input : undefined, + response_mode: this.responseMode, + response_type: "code" as const, + scope: options?.scope ?? this.clientMetadata.scope, + }; + + const authorizationUrl = new URL(metadata.authorization_endpoint); + + // Since the user will be redirected to the authorization_endpoint url using + // a browser, we need to make sure that the url is valid. + if ( + authorizationUrl.protocol !== "https:" && + authorizationUrl.protocol !== "http:" + ) { + throw new TypeError( + `Invalid authorization endpoint protocol: ${authorizationUrl.protocol}`, + ); + } + + if (metadata.pushed_authorization_request_endpoint) { + const server = await this.serverFactory.fromMetadata( + metadata, + authMethod, + dpopKey, + ); + const parResponse = await server.request( + "pushed_authorization_request", + parameters, + ); + + authorizationUrl.searchParams.set( + "client_id", + this.clientMetadata.client_id, + ); + authorizationUrl.searchParams.set("request_uri", parResponse.request_uri); + return authorizationUrl; + } else if (metadata.require_pushed_authorization_requests) { + throw new Error( + "Server requires pushed authorization requests (PAR) but no PAR endpoint is available", + ); + } else { + for (const [key, value] of Object.entries(parameters)) { + if (value) authorizationUrl.searchParams.set(key, String(value)); + } + + // Length of the URL that will be sent to the server + const urlLength = + authorizationUrl.pathname.length + authorizationUrl.search.length; + if (urlLength < 2048) { + return authorizationUrl; + } else if (!metadata.pushed_authorization_request_endpoint) { + throw new Error("Login URL too long"); + } + } + + throw new Error( + "Server does not support pushed authorization requests (PAR)", + ); + } +} diff --git a/apps/api/src/bsky/app.ts b/apps/api/src/bsky/app.ts index 1cdacb6a..abec11f1 100644 --- a/apps/api/src/bsky/app.ts +++ b/apps/api/src/bsky/app.ts @@ -17,6 +17,7 @@ import googleDriveAccounts from "schema/google-drive-accounts"; import spotifyAccounts from "schema/spotify-accounts"; import spotifyTokens from "schema/spotify-tokens"; import users from "schema/users"; +import { SCOPES } from "auth/client"; const app = new Hono(); @@ -31,7 +32,7 @@ app.get("/login", async (c) => { const url = await ctx.oauthClient.authorize( prompt ? "tsiry.selfhosted.social" : handle, { - scope: "atproto transition:generic", + scope: SCOPES.join(" "), // @ts-ignore: allow custom prompt param prompt, }, @@ -103,7 +104,7 @@ app.post("/login", async (c) => { } const url = await ctx.oauthClient.authorize(handle, { - scope: "atproto transition:generic", + scope: SCOPES.join(" "), }); if (cli) { @@ -321,4 +322,41 @@ app.get("/token", async (c) => { return c.json({ token }); }); +app.get("/oauth-client-metadata.json", (c) => + c.json(ctx.oauthClient.clientMetadata), +); + +app.get("/jwks.json", (c) => + c.json({ + keys: [ + { + kty: "EC", + use: "sig", + alg: "ES256", + kid: "2dfa3fd9-57b3-4738-ac27-9e6dadec13b7", + crv: "P-256", + x: "V_00KDnoEPsNqbt0y2Ke8v27Mv9WP70JylDUD5rvIek", + y: "HAyjaQeA2DU6wjZO0ggTadUS6ij1rmiYTxzmWeBKfRc", + }, + { + kty: "EC", + use: "sig", + alg: "ES256", + kid: "5e816ff2-6bff-4177-b1c0-67ad3cd3e7cd", + crv: "P-256", + x: "YwEY5NsoYQVB_G7xPYMl9sUtxRbcPFNffnZcTS5nbPQ", + y: "5n5mybPvISyYAnRv1Ii1geqKfXv2GA8p9Xemwx2a8CM", + }, + { + kty: "EC", + use: "sig", + kid: "a1067a48-a54a-43a0-9758-4d55b51fdd8b", + crv: "P-256", + x: "yq17Nd2DGcjP1i9I0NN3RBmgSbLQUZOtG6ec5GaqzmU", + y: "ieIU9mcfaZwAW5b3WgJkIRgddymG_ckcZ0n1XjbEIvc", + }, + ], + }), +); + export default app; diff --git a/apps/app-proxy/src/index.ts b/apps/app-proxy/src/index.ts index 5488950e..382538af 100644 --- a/apps/app-proxy/src/index.ts +++ b/apps/app-proxy/src/index.ts @@ -11,67 +11,15 @@ * Learn more at https://developers.cloudflare.com/workers/ */ -const metadata = { - redirect_uris: ['https://rocksky.app/oauth/callback'], - response_types: ['code'], - grant_types: ['authorization_code', 'refresh_token'], - scope: 'atproto transition:generic', - token_endpoint_auth_method: 'none', - application_type: 'web', - client_id: 'https://rocksky.app/oauth-client-metadata.json', - client_name: 'Rocksky', - client_uri: 'https://rocksky.app', - dpop_bound_access_tokens: true, -}; - -const jwks = { - keys: [ - { - kty: 'EC', - use: 'sig', - alg: 'ES256', - kid: '2dfa3fd9-57b3-4738-ac27-9e6dadec13b7', - crv: 'P-256', - x: 'V_00KDnoEPsNqbt0y2Ke8v27Mv9WP70JylDUD5rvIek', - y: 'HAyjaQeA2DU6wjZO0ggTadUS6ij1rmiYTxzmWeBKfRc', - }, - { - kty: 'EC', - use: 'sig', - alg: 'ES256', - kid: '5e816ff2-6bff-4177-b1c0-67ad3cd3e7cd', - crv: 'P-256', - x: 'YwEY5NsoYQVB_G7xPYMl9sUtxRbcPFNffnZcTS5nbPQ', - y: '5n5mybPvISyYAnRv1Ii1geqKfXv2GA8p9Xemwx2a8CM', - }, - { - kty: 'EC', - use: 'sig', - kid: 'a1067a48-a54a-43a0-9758-4d55b51fdd8b', - crv: 'P-256', - x: 'yq17Nd2DGcjP1i9I0NN3RBmgSbLQUZOtG6ec5GaqzmU', - y: 'ieIU9mcfaZwAW5b3WgJkIRgddymG_ckcZ0n1XjbEIvc', - }, - ], -}; - export default { async fetch(request, env, ctx): Promise { const url = new URL(request.url); let redirectToApi = false; - const API_ROUTES = ['/login', '/profile', '/token', '/now-playing', '/ws']; + const API_ROUTES = ['/login', '/profile', '/token', '/now-playing', '/ws', '/oauth-client-metadata.json', '/jwks.json']; console.log('Request URL:', url.pathname, url.pathname === '/client-metadata.json'); - if (url.pathname === '/oauth-client-metadata.json') { - return Response.json(metadata); - } - - if (url.pathname === '/jwks.json') { - return Response.json(jwks); - } - if ( API_ROUTES.includes(url.pathname) || url.pathname.startsWith('/oauth/callback') || diff --git a/bun.lock b/bun.lock index 8ee091e8..bbd4d771 100644 --- a/bun.lock +++ b/bun.lock @@ -17,10 +17,10 @@ "@atproto/api": "^0.13.31", "@atproto/common": "^0.4.6", "@atproto/identity": "^0.4.5", - "@atproto/jwk-jose": "0.1.5", + "@atproto/jwk-jose": "0.1.11", "@atproto/lex-cli": "^0.5.6", "@atproto/lexicon": "^0.4.5", - "@atproto/oauth-client-node": "0.2.14", + "@atproto/oauth-client-node": "0.3.16", "@atproto/sync": "^0.1.11", "@atproto/syntax": "^0.3.1", "@atproto/xrpc-server": "^0.7.8", @@ -372,23 +372,23 @@ "@asteasolutions/zod-to-openapi": ["@asteasolutions/zod-to-openapi@7.3.4", "", { "dependencies": { "openapi3-ts": "^4.1.2" }, "peerDependencies": { "zod": "^3.20.2" } }, "sha512-/2rThQ5zPi9OzVwes6U7lK1+Yvug0iXu25olp7S0XsYmOqnyMfxH7gdSQjn/+DSOHRg7wnotwGJSyL+fBKdnEA=="], - "@atproto-labs/did-resolver": ["@atproto-labs/did-resolver@0.1.11", "", { "dependencies": { "@atproto-labs/fetch": "0.2.2", "@atproto-labs/pipe": "0.1.0", "@atproto-labs/simple-store": "0.1.2", "@atproto-labs/simple-store-memory": "0.1.2", "@atproto/did": "0.1.5", "zod": "^3.23.8" } }, "sha512-qXNzIX2GPQnxT1gl35nv/8ErDdc4Fj/+RlJE7oyE7JGkFAPUyuY03TvKJ79SmWFsWE8wyTXEpLuphr9Da1Vhkw=="], + "@atproto-labs/did-resolver": ["@atproto-labs/did-resolver@0.2.6", "", { "dependencies": { "@atproto-labs/fetch": "0.2.3", "@atproto-labs/pipe": "0.1.1", "@atproto-labs/simple-store": "0.3.0", "@atproto-labs/simple-store-memory": "0.1.4", "@atproto/did": "0.3.0", "zod": "^3.23.8" } }, "sha512-2K1bC04nI2fmgNcvof+yA28IhGlpWn2JKYlPa7To9JTKI45FINCGkQSGiL2nyXlyzDJJ34fZ1aq6/IRFIOIiqg=="], - "@atproto-labs/fetch": ["@atproto-labs/fetch@0.2.2", "", { "dependencies": { "@atproto-labs/pipe": "0.1.0" } }, "sha512-QyafkedbFeVaN20DYUpnY2hcArYxjdThPXbYMqOSoZhcvkrUqaw4xDND4wZB5TBD9cq2yqe9V6mcw9P4XQKQuQ=="], + "@atproto-labs/fetch": ["@atproto-labs/fetch@0.2.3", "", { "dependencies": { "@atproto-labs/pipe": "0.1.1" } }, "sha512-NZtbJOCbxKUFRFKMpamT38PUQMY0hX0p7TG5AEYOPhZKZEP7dHZ1K2s1aB8MdVH0qxmqX7nQleNrrvLf09Zfdw=="], - "@atproto-labs/fetch-node": ["@atproto-labs/fetch-node@0.1.8", "", { "dependencies": { "@atproto-labs/fetch": "0.2.2", "@atproto-labs/pipe": "0.1.0", "ipaddr.js": "^2.1.0", "psl": "^1.9.0", "undici": "^6.14.1" } }, "sha512-OOTIhZNPEDDm7kaYU8iYRgzM+D5n3mP2iiBSyKuLakKTaZBL5WwYlUsJVsqX26SnUXtGEroOJEVJ6f66OcG80w=="], + "@atproto-labs/fetch-node": ["@atproto-labs/fetch-node@0.2.0", "", { "dependencies": { "@atproto-labs/fetch": "0.2.3", "@atproto-labs/pipe": "0.1.1", "ipaddr.js": "^2.1.0", "undici": "^6.14.1" } }, "sha512-Krq09nH/aeoiU2s9xdHA0FjTEFWG9B5FFenipv1iRixCcPc7V3DhTNDawxG9gI8Ny0k4dBVS9WTRN/IDzBx86Q=="], - "@atproto-labs/handle-resolver": ["@atproto-labs/handle-resolver@0.1.7", "", { "dependencies": { "@atproto-labs/simple-store": "0.1.2", "@atproto-labs/simple-store-memory": "0.1.2", "@atproto/did": "0.1.5", "zod": "^3.23.8" } }, "sha512-nb4uAOgRVMp2NGVTJnor4ohqySbd1KyB5VzQLaRjMaPwH60Al057eTqiKRbeH/xD7hOBPNj1m0YjgxzvyAnWkg=="], + "@atproto-labs/handle-resolver": ["@atproto-labs/handle-resolver@0.3.6", "", { "dependencies": { "@atproto-labs/simple-store": "0.3.0", "@atproto-labs/simple-store-memory": "0.1.4", "@atproto/did": "0.3.0", "zod": "^3.23.8" } }, "sha512-qnSTXvOBNj1EHhp2qTWSX8MS5q3AwYU5LKlt5fBvSbCjgmTr2j0URHCv+ydrwO55KvsojIkTMgeMOh4YuY4fCA=="], - "@atproto-labs/handle-resolver-node": ["@atproto-labs/handle-resolver-node@0.1.14", "", { "dependencies": { "@atproto-labs/fetch-node": "0.1.8", "@atproto-labs/handle-resolver": "0.1.7", "@atproto/did": "0.1.5" } }, "sha512-+kOf+xENdxUNrrLoIcp/L4ommIa1SHnwfHIWbxumXnacfurjMOnZhfXeiNsEguaAxDNYpqDNpKsFBtcgjffXvQ=="], + "@atproto-labs/handle-resolver-node": ["@atproto-labs/handle-resolver-node@0.1.25", "", { "dependencies": { "@atproto-labs/fetch-node": "0.2.0", "@atproto-labs/handle-resolver": "0.3.6", "@atproto/did": "0.3.0" } }, "sha512-NY9WYM2VLd3IuMGRkkmvGBg8xqVEaK/fitv1vD8SMXqFTekdpjOLCCyv7EFtqVHouzmDcL83VOvWRfHVa8V9Yw=="], - "@atproto-labs/identity-resolver": ["@atproto-labs/identity-resolver@0.1.15", "", { "dependencies": { "@atproto-labs/did-resolver": "0.1.11", "@atproto-labs/handle-resolver": "0.1.7", "@atproto/syntax": "0.4.0" } }, "sha512-3ABob5iUDoFL85I8/pJE4wncz3148fADoxNVAdksyACxxjpH1GNhSYNyIpRpdMCJ/kjj69DM9rggumTHqnD/Xg=="], + "@atproto-labs/identity-resolver": ["@atproto-labs/identity-resolver@0.3.6", "", { "dependencies": { "@atproto-labs/did-resolver": "0.2.6", "@atproto-labs/handle-resolver": "0.3.6" } }, "sha512-qoWqBDRobln0NR8L8dQjSp79E0chGkBhibEgxQa2f9WD+JbJdjQ0YvwwO5yeQn05pJoJmAwmI2wyJ45zjU7aWg=="], - "@atproto-labs/pipe": ["@atproto-labs/pipe@0.1.0", "", {}, "sha512-ghOqHFyJlQVFPESzlVHjKroP0tPzbmG5Jms0dNI9yLDEfL8xp4OFPWLX4f6T8mRq69wWs4nIDM3sSsFbFqLa1w=="], + "@atproto-labs/pipe": ["@atproto-labs/pipe@0.1.1", "", {}, "sha512-hdNw2oUs2B6BN1lp+32pF7cp8EMKuIN5Qok2Vvv/aOpG/3tNSJ9YkvfI0k6Zd188LeDDYRUpYpxcoFIcGH/FNg=="], - "@atproto-labs/simple-store": ["@atproto-labs/simple-store@0.1.2", "", {}, "sha512-9vTNvyPPBs44tKVFht16wGlilW8u4wpEtKwLkWbuNEh3h9TTQ8zjVhEoGZh/v73G4Otr9JUOSIq+/5+8OZD2mQ=="], + "@atproto-labs/simple-store": ["@atproto-labs/simple-store@0.3.0", "", {}, "sha512-nOb6ONKBRJHRlukW1sVawUkBqReLlLx6hT35VS3imaNPwiXDxLnTK7lxw3Lrl9k5yugSBDQAkZAq3MPTEFSUBQ=="], - "@atproto-labs/simple-store-memory": ["@atproto-labs/simple-store-memory@0.1.2", "", { "dependencies": { "@atproto-labs/simple-store": "0.1.2", "lru-cache": "^10.2.0" } }, "sha512-q6wawjKKXuhUzr2MnkSlgr6zU6VimYkL8eNvLQvkroLnIDyMkoCKO4+EJ885ZD8lGwBo4pX9Lhrg9JJ+ncJI8g=="], + "@atproto-labs/simple-store-memory": ["@atproto-labs/simple-store-memory@0.1.4", "", { "dependencies": { "@atproto-labs/simple-store": "0.3.0", "lru-cache": "^10.2.0" } }, "sha512-3mKY4dP8I7yKPFj9VKpYyCRzGJOi5CEpOLPlRhoJyLmgs3J4RzDrjn323Oakjz2Aj2JzRU/AIvWRAZVhpYNJHw=="], "@atproto/api": ["@atproto/api@0.13.35", "", { "dependencies": { "@atproto/common-web": "^0.4.0", "@atproto/lexicon": "^0.4.6", "@atproto/syntax": "^0.3.2", "@atproto/xrpc": "^0.6.8", "await-lock": "^2.2.2", "multiformats": "^9.9.0", "tlds": "^1.234.0", "zod": "^3.23.8" } }, "sha512-vsEfBj0C333TLjDppvTdTE0IdKlXuljKSveAeI4PPx/l6eUKNnDTsYxvILtXUVzwUlTDmSRqy5O4Ryh78n1b7g=="], @@ -398,25 +398,29 @@ "@atproto/crypto": ["@atproto/crypto@0.4.4", "", { "dependencies": { "@noble/curves": "^1.7.0", "@noble/hashes": "^1.6.1", "uint8arrays": "3.0.0" } }, "sha512-Yq9+crJ7WQl7sxStVpHgie5Z51R05etaK9DLWYG/7bR5T4bhdcIgF6IfklLShtZwLYdVVj+K15s0BqW9a8PSDA=="], - "@atproto/did": ["@atproto/did@0.1.5", "", { "dependencies": { "zod": "^3.23.8" } }, "sha512-8+1D08QdGE5TF0bB0vV8HLVrVZJeLNITpRTUVEoABNMRaUS7CoYSVb0+JNQDeJIVmqMjOL8dOjvCUDkp3gEaGQ=="], + "@atproto/did": ["@atproto/did@0.3.0", "", { "dependencies": { "zod": "^3.23.8" } }, "sha512-raUPzUGegtW/6OxwCmM8bhZvuIMzxG5t9oWsth6Tp91Kb5fTnHV2h/KKNF1C82doeA4BdXCErTyg7ISwLbQkzA=="], "@atproto/identity": ["@atproto/identity@0.4.9", "", { "dependencies": { "@atproto/common-web": "^0.4.3", "@atproto/crypto": "^0.4.4" } }, "sha512-pRYCaeaEJMZ4vQlRQYYTrF3cMiRp21n/k/pUT1o7dgKby56zuLErDmFXkbKfKWPf7SgWRgamSaNmsGLqAOD7lQ=="], - "@atproto/jwk": ["@atproto/jwk@0.1.4", "", { "dependencies": { "multiformats": "^9.9.0", "zod": "^3.23.8" } }, "sha512-dSRuEi0FbxL5ln6hEFHp5ZW01xbQH9yJi5odZaEYpcA6beZHf/bawlU12CQy/CDsbC3FxSqrBw7Q2t7mvdSBqw=="], + "@atproto/jwk": ["@atproto/jwk@0.6.0", "", { "dependencies": { "multiformats": "^9.9.0", "zod": "^3.23.8" } }, "sha512-bDoJPvt7TrQVi/rBfBrSSpGykhtIriKxeYCYQTiPRKFfyRhbgpElF0wPXADjIswnbzZdOwbY63az4E/CFVT3Tw=="], - "@atproto/jwk-jose": ["@atproto/jwk-jose@0.1.5", "", { "dependencies": { "@atproto/jwk": "0.1.4", "jose": "^5.2.0" } }, "sha512-piYZ3ohKhRiGlD6/bZCV/Ed3lIi7CVd6txbofEHik22EkYWK0nWKoEriCUSTssSylwFzeOq2r31Ut16WcJoghw=="], + "@atproto/jwk-jose": ["@atproto/jwk-jose@0.1.11", "", { "dependencies": { "@atproto/jwk": "0.6.0", "jose": "^5.2.0" } }, "sha512-i4Fnr2sTBYmMmHXl7NJh8GrCH+tDQEVWrcDMDnV5DjJfkgT17wIqvojIw9SNbSL4Uf0OtfEv6AgG0A+mgh8b5Q=="], - "@atproto/jwk-webcrypto": ["@atproto/jwk-webcrypto@0.1.5", "", { "dependencies": { "@atproto/jwk": "0.1.4", "@atproto/jwk-jose": "0.1.5", "zod": "^3.23.8" } }, "sha512-xsX8cJO6rBakLz8zNKenuKIbjoTeaiMi/ETOFFYGtlMlk1grdxDOe6OGpCmUDXaOiYWu3x5K5Hc4J9ooI/4nRg=="], + "@atproto/jwk-webcrypto": ["@atproto/jwk-webcrypto@0.2.0", "", { "dependencies": { "@atproto/jwk": "0.6.0", "@atproto/jwk-jose": "0.1.11", "zod": "^3.23.8" } }, "sha512-UmgRrrEAkWvxwhlwe30UmDOdTEFidlIzBC7C3cCbeJMcBN1x8B3KH+crXrsTqfWQBG58mXgt8wgSK3Kxs2LhFg=="], "@atproto/lex-cli": ["@atproto/lex-cli@0.5.7", "", { "dependencies": { "@atproto/lexicon": "^0.4.6", "@atproto/syntax": "^0.3.2", "chalk": "^4.1.2", "commander": "^9.4.0", "prettier": "^3.2.5", "ts-morph": "^16.0.0", "yesno": "^0.4.0", "zod": "^3.23.8" }, "bin": { "lex": "dist/index.js" } }, "sha512-V5rsU95Th57KICxUGwTjudN5wmFBHL/fLkl7banl6izsQBiUrVvrj3EScNW/Wx2PnwlJwxtTpa1rTnP30+i5/A=="], + "@atproto/lex-data": ["@atproto/lex-data@0.0.9", "", { "dependencies": { "multiformats": "^9.9.0", "tslib": "^2.8.1", "uint8arrays": "3.0.0", "unicode-segmenter": "^0.14.0" } }, "sha512-1slwe4sG0cyWtsq16+rBoWIxNDqGPkkvN+PV6JuzA7dgUK9bjUmXBGQU4eZlUPSS43X1Nhmr/9VjgKmEzU9vDw=="], + + "@atproto/lex-json": ["@atproto/lex-json@0.0.9", "", { "dependencies": { "@atproto/lex-data": "0.0.9", "tslib": "^2.8.1" } }, "sha512-Q2v1EVZcnd+ndyZj1r2UlGikA7q6It24CFPLbxokcf5Ba4RBupH8IkkQX7mqUDSRWPgQdmZYIdW9wUln+MKDqw=="], + "@atproto/lexicon": ["@atproto/lexicon@0.4.14", "", { "dependencies": { "@atproto/common-web": "^0.4.2", "@atproto/syntax": "^0.4.0", "iso-datestring-validator": "^2.2.2", "multiformats": "^9.9.0", "zod": "^3.23.8" } }, "sha512-jiKpmH1QER3Gvc7JVY5brwrfo+etFoe57tKPQX/SmPwjvUsFnJAow5xLIryuBaJgFAhnTZViXKs41t//pahGHQ=="], - "@atproto/oauth-client": ["@atproto/oauth-client@0.3.13", "", { "dependencies": { "@atproto-labs/did-resolver": "0.1.11", "@atproto-labs/fetch": "0.2.2", "@atproto-labs/handle-resolver": "0.1.7", "@atproto-labs/identity-resolver": "0.1.15", "@atproto-labs/simple-store": "0.1.2", "@atproto-labs/simple-store-memory": "0.1.2", "@atproto/did": "0.1.5", "@atproto/jwk": "0.1.4", "@atproto/oauth-types": "0.2.4", "@atproto/xrpc": "0.6.12", "multiformats": "^9.9.0", "zod": "^3.23.8" } }, "sha512-PqE6hWG6bhpu5OUbccoAZjoj9LQroStuPjXqkHCsnUfQGmruzuNmzMS0myLdoWCx+NSGr4sMgUPjGzAHXSLoaQ=="], + "@atproto/oauth-client": ["@atproto/oauth-client@0.5.14", "", { "dependencies": { "@atproto-labs/did-resolver": "0.2.6", "@atproto-labs/fetch": "0.2.3", "@atproto-labs/handle-resolver": "0.3.6", "@atproto-labs/identity-resolver": "0.3.6", "@atproto-labs/simple-store": "0.3.0", "@atproto-labs/simple-store-memory": "0.1.4", "@atproto/did": "0.3.0", "@atproto/jwk": "0.6.0", "@atproto/oauth-types": "0.6.2", "@atproto/xrpc": "0.7.7", "core-js": "^3", "multiformats": "^9.9.0", "zod": "^3.23.8" } }, "sha512-sPH+vcdq9maTEAhJI0HzmFcFAMrkCS19np+RUssNkX6kS8Xr3OYr57tvYRCbkcnIyYTfYcxKQgpwHKx3RVEaYw=="], - "@atproto/oauth-client-node": ["@atproto/oauth-client-node@0.2.14", "", { "dependencies": { "@atproto-labs/did-resolver": "0.1.11", "@atproto-labs/handle-resolver-node": "0.1.14", "@atproto-labs/simple-store": "0.1.2", "@atproto/did": "0.1.5", "@atproto/jwk": "0.1.4", "@atproto/jwk-jose": "0.1.5", "@atproto/jwk-webcrypto": "0.1.5", "@atproto/oauth-client": "0.3.13", "@atproto/oauth-types": "0.2.4" } }, "sha512-KDQWhkCqwVJtuBmqBLRo9sOL9Mw9SkFmCe1s+t6asdkfe1uMezvSTYCi5SIR+E4vwrYSIq2z6ZvWbc/XV3UwEQ=="], + "@atproto/oauth-client-node": ["@atproto/oauth-client-node@0.3.16", "", { "dependencies": { "@atproto-labs/did-resolver": "0.2.6", "@atproto-labs/handle-resolver-node": "0.1.25", "@atproto-labs/simple-store": "0.3.0", "@atproto/did": "0.3.0", "@atproto/jwk": "0.6.0", "@atproto/jwk-jose": "0.1.11", "@atproto/jwk-webcrypto": "0.2.0", "@atproto/oauth-client": "0.5.14", "@atproto/oauth-types": "0.6.2" } }, "sha512-2dooMzxAkiQ4MkOAZlEQ3iwbB9SEovrbIKMNuBbVCLQYORVNxe20tMdjs3lvhrzdpzvaHLlQnJJhw5dA9VELFw=="], - "@atproto/oauth-types": ["@atproto/oauth-types@0.2.4", "", { "dependencies": { "@atproto/jwk": "0.1.4", "zod": "^3.23.8" } }, "sha512-V2LnlXi1CSmBQWTQgDm8l4oN7xYxlftVwM7hrvYNP+Jxo3Ozfe0QLK1Wy/CH6/ZqzrBBhYvcbf4DJYTUwPA+hw=="], + "@atproto/oauth-types": ["@atproto/oauth-types@0.6.2", "", { "dependencies": { "@atproto/did": "0.3.0", "@atproto/jwk": "0.6.0", "zod": "^3.23.8" } }, "sha512-2cuboM4RQBCYR8NQC5uGRkW6KgCgKyq/B5/+tnMmWZYtZGVUQvsUWQHK/ZiMCnVXbcDNtc/RIEJQJDZ8FXMoxg=="], "@atproto/repo": ["@atproto/repo@0.6.5", "", { "dependencies": { "@atproto/common": "^0.4.8", "@atproto/common-web": "^0.4.0", "@atproto/crypto": "^0.4.4", "@atproto/lexicon": "^0.4.7", "@ipld/car": "^3.2.3", "@ipld/dag-cbor": "^7.0.0", "multiformats": "^9.9.0", "uint8arrays": "3.0.0", "zod": "^3.23.8" } }, "sha512-Sa95LaEMDtwL9M0kp3vuVQIcgEJI+6EssDLIiuPnJAi9SbEPESdUfEiIR5t2oFCkMwrS7OJQCLdCa7CMy+plUg=="], @@ -2464,8 +2468,6 @@ "proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="], - "psl": ["psl@1.15.0", "", { "dependencies": { "punycode": "^2.3.1" } }, "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w=="], - "pump": ["pump@3.0.3", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA=="], "punycode": ["punycode@1.4.1", "", {}, "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ=="], @@ -2834,6 +2836,8 @@ "unenv": ["unenv@2.0.0-rc.14", "", { "dependencies": { "defu": "^6.1.4", "exsolve": "^1.0.1", "ohash": "^2.0.10", "pathe": "^2.0.3", "ufo": "^1.5.4" } }, "sha512-od496pShMen7nOy5VmVJCnq8rptd45vh6Nx/r2iPbrba6pa6p+tS2ywuIHRZ/OBvSbQZB0kWvpO9XBNVFXHD3Q=="], + "unicode-segmenter": ["unicode-segmenter@0.14.5", "", {}, "sha512-jHGmj2LUuqDcX3hqY12Ql+uhUTn8huuxNZGq7GvtF6bSybzH3aFgedYu/KTzQStEgt1Ra2F3HxadNXsNjb3m3g=="], + "universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="], "unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="], @@ -2944,8 +2948,6 @@ "@atproto-labs/fetch-node/undici": ["undici@6.22.0", "", {}, "sha512-hU/10obOIu62MGYjdskASR3CUAiYaFTtC9Pa6vHyf//mAipSvSQg6od2CnJswq7fvzNS3zJhxoRkgNVaHurWKw=="], - "@atproto-labs/identity-resolver/@atproto/syntax": ["@atproto/syntax@0.4.0", "", {}, "sha512-b9y5ceHS8YKOfP3mdKmwAx5yVj9294UN7FG2XzP6V5aKUdFazEYRnR9m5n5ZQFKa3GNvz7de9guZCJ/sUTcOAA=="], - "@atproto/common/@ipld/dag-cbor": ["@ipld/dag-cbor@7.0.3", "", { "dependencies": { "cborg": "^1.6.0", "multiformats": "^9.5.4" } }, "sha512-1VVh2huHsuohdXC1bGJNE8WR72slZ9XE2T3wbBBq31dm7ZBatmKLLxrB+XAqafxfRFjv08RZmj/W/ZqaM13AuA=="], "@atproto/crypto/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], @@ -2958,6 +2960,8 @@ "@atproto/lexicon/@atproto/syntax": ["@atproto/syntax@0.4.0", "", {}, "sha512-b9y5ceHS8YKOfP3mdKmwAx5yVj9294UN7FG2XzP6V5aKUdFazEYRnR9m5n5ZQFKa3GNvz7de9guZCJ/sUTcOAA=="], + "@atproto/oauth-client/@atproto/xrpc": ["@atproto/xrpc@0.7.7", "", { "dependencies": { "@atproto/lexicon": "^0.6.0", "zod": "^3.23.8" } }, "sha512-K1ZyO/BU8JNtXX5dmPp7b5UrkLMMqpsIa/Lrj5D3Su+j1Xwq1m6QJ2XJ1AgjEjkI1v4Muzm7klianLE6XGxtmA=="], + "@atproto/repo/@ipld/dag-cbor": ["@ipld/dag-cbor@7.0.3", "", { "dependencies": { "cborg": "^1.6.0", "multiformats": "^9.5.4" } }, "sha512-1VVh2huHsuohdXC1bGJNE8WR72slZ9XE2T3wbBBq31dm7ZBatmKLLxrB+XAqafxfRFjv08RZmj/W/ZqaM13AuA=="], "@atproto/sync/@atproto/lexicon": ["@atproto/lexicon@0.5.1", "", { "dependencies": { "@atproto/common-web": "^0.4.3", "@atproto/syntax": "^0.4.1", "iso-datestring-validator": "^2.2.2", "multiformats": "^9.9.0", "zod": "^3.23.8" } }, "sha512-y8AEtYmfgVl4fqFxqXAeGvhesiGkxiy3CWoJIfsFDDdTlZUC8DFnZrYhcqkIop3OlCkkljvpSJi1hbeC1tbi8A=="], @@ -3102,6 +3106,8 @@ "@poppinss/dumper/supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="], + "@rocksky/cli/@atproto/jwk-jose": ["@atproto/jwk-jose@0.1.5", "", { "dependencies": { "@atproto/jwk": "0.1.4", "jose": "^5.2.0" } }, "sha512-piYZ3ohKhRiGlD6/bZCV/Ed3lIi7CVd6txbofEHik22EkYWK0nWKoEriCUSTssSylwFzeOq2r31Ut16WcJoghw=="], + "@rocksky/cli/drizzle-orm": ["drizzle-orm@0.45.1", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-Te0FOdKIistGNPMq2jscdqngBRfBpC8uMFVwqjf6gtTVJHIQ/dosgV/CLBU2N4ZJBsXL5savCba9b0YJskKdcA=="], "@rocksky/cli/uuid": ["uuid@13.0.0", "", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w=="], @@ -3264,8 +3270,6 @@ "prop-types-extra/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="], - "psl/punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], - "raw-body/iconv-lite": ["iconv-lite@0.7.0", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ=="], "rc/strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="], @@ -3356,6 +3360,8 @@ "@atproto/lex-cli/chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + "@atproto/oauth-client/@atproto/xrpc/@atproto/lexicon": ["@atproto/lexicon@0.6.1", "", { "dependencies": { "@atproto/common-web": "^0.4.13", "@atproto/syntax": "^0.4.3", "iso-datestring-validator": "^2.2.2", "multiformats": "^9.9.0", "zod": "^3.23.8" } }, "sha512-/vI1kVlY50Si+5MXpvOucelnYwb0UJ6Qto5mCp+7Q5C+Jtp+SoSykAPVvjVtTnQUH2vrKOFOwpb3C375vSKzXw=="], + "@atproto/repo/@ipld/dag-cbor/cborg": ["cborg@1.10.2", "", { "bin": { "cborg": "cli.js" } }, "sha512-b3tFPA9pUr2zCUiCfRd2+wok2/LBSNUMKOuRRok+WlvvAgEt/PlbgPTsZUcwCOs53IJvLgTp0eotwtosE6njug=="], "@atproto/sync/@atproto/repo/@ipld/dag-cbor": ["@ipld/dag-cbor@7.0.3", "", { "dependencies": { "cborg": "^1.6.0", "multiformats": "^9.5.4" } }, "sha512-1VVh2huHsuohdXC1bGJNE8WR72slZ9XE2T3wbBBq31dm7ZBatmKLLxrB+XAqafxfRFjv08RZmj/W/ZqaM13AuA=="], @@ -3500,6 +3506,10 @@ "@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + "@rocksky/cli/@atproto/jwk-jose/@atproto/jwk": ["@atproto/jwk@0.1.4", "", { "dependencies": { "multiformats": "^9.9.0", "zod": "^3.23.8" } }, "sha512-dSRuEi0FbxL5ln6hEFHp5ZW01xbQH9yJi5odZaEYpcA6beZHf/bawlU12CQy/CDsbC3FxSqrBw7Q2t7mvdSBqw=="], + + "@rocksky/cli/@atproto/jwk-jose/jose": ["jose@5.10.0", "", {}, "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg=="], + "@rocksky/doc/vitest/@vitest/expect": ["@vitest/expect@2.1.9", "", { "dependencies": { "@vitest/spy": "2.1.9", "@vitest/utils": "2.1.9", "chai": "^5.1.2", "tinyrainbow": "^1.2.0" } }, "sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw=="], "@rocksky/doc/vitest/@vitest/mocker": ["@vitest/mocker@2.1.9", "", { "dependencies": { "@vitest/spy": "2.1.9", "estree-walker": "^3.0.3", "magic-string": "^0.30.12" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^5.0.0" }, "optionalPeers": ["msw", "vite"] }, "sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg=="], @@ -3726,6 +3736,10 @@ "wrangler/miniflare/zod": ["zod@3.22.3", "", {}, "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug=="], + "@atproto/oauth-client/@atproto/xrpc/@atproto/lexicon/@atproto/common-web": ["@atproto/common-web@0.4.14", "", { "dependencies": { "@atproto/lex-data": "0.0.9", "@atproto/lex-json": "0.0.9", "@atproto/syntax": "0.4.3", "zod": "^3.23.8" } }, "sha512-rMU8Q+kpyPpirUS9OqT7aOD1hxKa+diem3vc7BA0lOkj0tU6wcAxegxmbPZ8JaOsR7SSYhP/jCt/5wbT4qqkuQ=="], + + "@atproto/oauth-client/@atproto/xrpc/@atproto/lexicon/@atproto/syntax": ["@atproto/syntax@0.4.3", "", { "dependencies": { "tslib": "^2.8.1" } }, "sha512-YoZUz40YAJr5nPwvCDWgodEOlt5IftZqPJvA0JDWjuZKD8yXddTwSzXSaKQAzGOpuM+/A3uXRtPzJJqlScc+iA=="], + "@atproto/sync/@atproto/repo/@ipld/dag-cbor/cborg": ["cborg@1.10.2", "", { "bin": { "cborg": "cli.js" } }, "sha512-b3tFPA9pUr2zCUiCfRd2+wok2/LBSNUMKOuRRok+WlvvAgEt/PlbgPTsZUcwCOs53IJvLgTp0eotwtosE6njug=="], "@atproto/sync/@atproto/xrpc-server/express/accepts": ["accepts@1.3.8", "", { "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" } }, "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw=="], diff --git a/tools/.zed/settings.json b/tools/.zed/settings.json new file mode 100644 index 00000000..ae4f3e38 --- /dev/null +++ b/tools/.zed/settings.json @@ -0,0 +1,25 @@ +{ + "lsp": { + "deno": { + "settings": { + "deno": { + "enable": true, + "unstable": false, + "lint": true, + "cache": null + } + } + } + }, + "languages": { + "TypeScript": { + "language_servers": ["deno", "!typescript-language-server"] + }, + "TSX": { + "language_servers": ["deno", "!typescript-language-server"] + }, + "JavaScript": { + "language_servers": ["deno", "!typescript-language-server"] + } + } +} diff --git a/tools/deno.json b/tools/deno.json index b229ff77..b077ac50 100644 --- a/tools/deno.json +++ b/tools/deno.json @@ -1,9 +1,10 @@ { "tasks": { - "cron": "deno run cron.ts" + "cron": "deno run cron.ts", + "local-proxy": "deno run -A local-proxy.ts" }, "imports": { "@std/assert": "jsr:@std/assert@1", "chalk": "npm:chalk@^5.6.2" } -} \ No newline at end of file +} diff --git a/tools/deno.lock b/tools/deno.lock index 3fdfd6b7..3a6a089f 100644 --- a/tools/deno.lock +++ b/tools/deno.lock @@ -1,13 +1,13 @@ { "version": "5", "specifiers": { - "jsr:@std/assert@1": "1.0.15", + "jsr:@std/assert@1": "1.0.16", "jsr:@std/internal@^1.0.12": "1.0.12", "npm:chalk@^5.6.2": "5.6.2" }, "jsr": { - "@std/assert@1.0.15": { - "integrity": "d64018e951dbdfab9777335ecdb000c0b4e3df036984083be219ce5941e4703b", + "@std/assert@1.0.16": { + "integrity": "6a7272ed1eaa77defe76e5ff63ca705d9c495077e2d5fd0126d2b53fc5bd6532", "dependencies": [ "jsr:@std/internal" ] diff --git a/tools/local-proxy.ts b/tools/local-proxy.ts new file mode 100644 index 00000000..4babcc6e --- /dev/null +++ b/tools/local-proxy.ts @@ -0,0 +1,90 @@ +const PORT = parseInt(Deno.env.get("PORT") || "8081"); + +console.log(`HTTP Proxy Server is running on http://localhost:${PORT}`); + +Deno.serve({ port: PORT }, async (req) => { + const url = new URL(req.url); + + try { + let target: URL; + + if ( + url.pathname.startsWith("/xrpc") || + url.pathname.startsWith("/login") || + url.pathname.startsWith("/oauth/callback") || + url.pathname.startsWith("/users") || + url.pathname.startsWith("/albums") || + url.pathname.startsWith("/artists") || + url.pathname.startsWith("/tracks") || + url.pathname.startsWith("/scrobbles") || + url.pathname.startsWith("/likes") || + url.pathname.startsWith("/spotify") || + url.pathname.startsWith("/dropbox/oauth/callback") || + url.pathname.startsWith("/googledrive/oauth/callback") || + url.pathname.startsWith("/dropbox/files") || + url.pathname.startsWith("/dropbox/file") || + url.pathname.startsWith("/googledrive/files") || + url.pathname.startsWith("/dropbox/login") || + url.pathname.startsWith("/googledrive/login") || + url.pathname.startsWith("/dropbox/join") || + url.pathname.startsWith("/googledrive/join") || + url.pathname.startsWith("/search") || + url.pathname.startsWith("/public/scrobbles") + ) { + // API requests + target = new URL(url); + target.host = "localhost"; + target.port = "4004"; + } else { + // Vite frontend requests + target = new URL(url); + target.host = "localhost"; + target.port = "5174"; + } + + // Handle WebSocket connections + if (req.headers.get("upgrade") === "websocket") { + const { socket, response } = Deno.upgradeWebSocket(req); + const wsUrl = `ws://${target.host}${target.pathname}${target.search}`; + const ws = new WebSocket(wsUrl); + + ws.onopen = () => { + console.log("WebSocket connection established"); + }; + ws.onmessage = (event) => { + socket.send(event.data); + }; + ws.onclose = () => { + socket.close(); + }; + ws.onerror = (event) => { + console.error("WebSocket error:", event); + socket.close(); + }; + socket.onmessage = (event) => { + ws.send(event.data); + }; + socket.onclose = () => { + ws.close(); + }; + return response; + } + + // Proxy HTTP requests + const proxyRequest = new Request(target.toString(), { + method: req.method, + headers: req.headers, + body: req.body, + }); + + const response = await fetch(proxyRequest); + + return new Response(response.body, { + status: response.status, + headers: response.headers, + }); + } catch (error) { + console.error("Proxy error:", error); + return new Response("Failed to fetch the target URL", { status: 500 }); + } +});