A decentralized music tracking and discovery platform built on AT Protocol 🎵

Merge pull request #16 from tsirysndr/atproto-oauth-upgrade

Upgrade and refactor OAuth client

authored by tsiry-sandratraina.com and committed by

GitHub fb28db8c 98aa765c

+421 -196
+2 -2
apps/api/package.json
··· 34 34 "@atproto/api": "^0.13.31", 35 35 "@atproto/common": "^0.4.6", 36 36 "@atproto/identity": "^0.4.5", 37 - "@atproto/jwk-jose": "0.1.5", 37 + "@atproto/jwk-jose": "0.1.11", 38 38 "@atproto/lex-cli": "^0.5.6", 39 39 "@atproto/lexicon": "^0.4.5", 40 - "@atproto/oauth-client-node": "0.2.14", 40 + "@atproto/oauth-client-node": "0.3.16", 41 41 "@atproto/sync": "^0.1.11", 42 42 "@atproto/syntax": "^0.3.1", 43 43 "@atproto/xrpc-server": "^0.7.8",
+18 -109
apps/api/src/auth/client.ts
··· 1 1 import { JoseKey } from "@atproto/jwk-jose"; 2 - import { 3 - AuthorizeOptions, 4 - NodeOAuthClient, 5 - NodeOAuthClientOptions, 6 - OAuthAuthorizationRequestParameters, 7 - type RuntimeLock, 8 - } from "@atproto/oauth-client-node"; 2 + import type { RuntimeLock } from "@atproto/oauth-client-node"; 9 3 import Redis from "ioredis"; 10 4 import Redlock from "redlock"; 11 5 import type { Database } from "../db"; 12 6 import { env } from "../lib/env"; 13 7 import { SessionStore, StateStore } from "./storage"; 14 - 15 - export const FALLBACK_ALG = "ES256"; 16 - 17 - export class CustomOAuthClient extends NodeOAuthClient { 18 - constructor(options: NodeOAuthClientOptions) { 19 - super(options); 20 - } 21 - 22 - async authorize( 23 - input: string, 24 - { signal, ...options }: AuthorizeOptions = {}, 25 - ): Promise<URL> { 26 - const redirectUri = 27 - options?.redirect_uri ?? this.clientMetadata.redirect_uris[0]; 28 - if (!this.clientMetadata.redirect_uris.includes(redirectUri)) { 29 - // The server will enforce this, but let's catch it early 30 - throw new TypeError("Invalid redirect_uri"); 31 - } 32 - 33 - const { identity, metadata } = await this.oauthResolver.resolve(input, { 34 - signal, 35 - }); 36 - 37 - const pkce = await this.runtime.generatePKCE(); 38 - const dpopKey = await this.runtime.generateKey( 39 - metadata.dpop_signing_alg_values_supported || [FALLBACK_ALG], 40 - ); 41 - 42 - const state = await this.runtime.generateNonce(); 43 - 44 - await this.stateStore.set(state, { 45 - iss: metadata.issuer, 46 - dpopKey, 47 - verifier: pkce.verifier, 48 - appState: options?.state, 49 - }); 8 + import { CustomOAuthClient } from "./oauth-client"; 50 9 51 - const parameters: OAuthAuthorizationRequestParameters = { 52 - ...options, 53 - 54 - client_id: this.clientMetadata.client_id, 55 - redirect_uri: redirectUri, 56 - code_challenge: pkce.challenge, 57 - code_challenge_method: pkce.method, 58 - state, 59 - login_hint: identity && !options.prompt ? input : undefined, 60 - response_mode: this.responseMode, 61 - response_type: "code" as const, 62 - scope: options?.scope ?? this.clientMetadata.scope, 63 - }; 64 - 65 - const authorizationUrl = new URL(metadata.authorization_endpoint); 66 - 67 - // Since the user will be redirected to the authorization_endpoint url using 68 - // a browser, we need to make sure that the url is valid. 69 - if ( 70 - authorizationUrl.protocol !== "https:" && 71 - authorizationUrl.protocol !== "http:" 72 - ) { 73 - throw new TypeError( 74 - `Invalid authorization endpoint protocol: ${authorizationUrl.protocol}`, 75 - ); 76 - } 77 - 78 - if (metadata.pushed_authorization_request_endpoint) { 79 - const server = await this.serverFactory.fromMetadata(metadata, dpopKey); 80 - const parResponse = await server.request( 81 - "pushed_authorization_request", 82 - parameters, 83 - ); 84 - 85 - authorizationUrl.searchParams.set( 86 - "client_id", 87 - this.clientMetadata.client_id, 88 - ); 89 - authorizationUrl.searchParams.set("request_uri", parResponse.request_uri); 90 - return authorizationUrl; 91 - } else if (metadata.require_pushed_authorization_requests) { 92 - throw new Error( 93 - "Server requires pushed authorization requests (PAR) but no PAR endpoint is available", 94 - ); 95 - } else { 96 - for (const [key, value] of Object.entries(parameters)) { 97 - if (value) authorizationUrl.searchParams.set(key, String(value)); 98 - } 99 - 100 - // Length of the URL that will be sent to the server 101 - const urlLength = 102 - authorizationUrl.pathname.length + authorizationUrl.search.length; 103 - if (urlLength < 2048) { 104 - return authorizationUrl; 105 - } else if (!metadata.pushed_authorization_request_endpoint) { 106 - throw new Error("Login URL too long"); 107 - } 108 - } 109 - 110 - throw new Error( 111 - "Server does not support pushed authorization requests (PAR)", 112 - ); 113 - } 114 - } 10 + export const SCOPES = [ 11 + "atproto", 12 + "repo:app.rocksky.album", 13 + "repo:app.rocksky.artist", 14 + "repo:app.rocksky.graph.follow", 15 + "repo:app.rocksky.like", 16 + "repo:app.rocksky.playlist", 17 + "repo:app.rocksky.scrobble", 18 + "repo:app.rocksky.shout", 19 + "repo:app.rocksky.song", 20 + "repo:app.rocksky.feed.generator", 21 + "repo:fm.teal.alpha.feed.play", 22 + "repo:fm.teal.alpha.actor.status", 23 + ]; 115 24 116 25 export const createClient = async (db: Database) => { 117 26 const publicUrl = env.PUBLIC_URL; ··· 139 48 ? `${url}/oauth-client-metadata.json` 140 49 : `http://localhost?redirect_uri=${enc( 141 50 `${url}/oauth/callback`, 142 - )}&scope=${enc("atproto transition:generic")}`, 51 + )}&scope=${enc(SCOPES.join(" "))}`, 143 52 client_uri: url, 144 53 redirect_uris: [`${url}/oauth/callback`], 145 - scope: "atproto transition:generic", 54 + scope: SCOPES.join(" "), 146 55 grant_types: ["authorization_code", "refresh_token"], 147 56 response_types: ["code"], 148 57 application_type: "web",
+84
apps/api/src/auth/oauth-client-auth.ts
··· 1 + import type { 2 + ClientMetadata, 3 + Keyset, 4 + OAuthAuthorizationServerMetadata, 5 + } from "@atproto/oauth-client-node"; 6 + 7 + import type { ClientAuthMethod } from "@atproto/oauth-client/dist/oauth-client-auth"; 8 + 9 + export const FALLBACK_ALG = "ES256"; 10 + 11 + function supportedMethods(serverMetadata: OAuthAuthorizationServerMetadata) { 12 + return serverMetadata["token_endpoint_auth_methods_supported"]; 13 + } 14 + 15 + function supportedAlgs(serverMetadata: OAuthAuthorizationServerMetadata) { 16 + return ( 17 + serverMetadata["token_endpoint_auth_signing_alg_values_supported"] ?? [ 18 + // @NOTE If not specified, assume that the server supports the ES256 19 + // algorithm, as prescribed by the spec: 20 + // 21 + // > Clients and Authorization Servers currently must support the ES256 22 + // > cryptographic system [for client authentication]. 23 + // 24 + // https://atproto.com/specs/oauth#confidential-client-authentication 25 + FALLBACK_ALG, 26 + ] 27 + ); 28 + } 29 + 30 + export function negotiateClientAuthMethod( 31 + serverMetadata: OAuthAuthorizationServerMetadata, 32 + clientMetadata: ClientMetadata, 33 + keyset?: Keyset, 34 + ): ClientAuthMethod { 35 + const method = clientMetadata.token_endpoint_auth_method; 36 + 37 + // @NOTE ATproto spec requires that AS support both "none" and 38 + // "private_key_jwt", and that clients use one of the other. The following 39 + // check ensures that the AS is indeed compliant with this client's 40 + // configuration. 41 + const methods = supportedMethods(serverMetadata); 42 + if (!methods.includes(method)) { 43 + throw new Error( 44 + `The server does not support "${method}" authentication. Supported methods are: ${methods.join( 45 + ", ", 46 + )}.`, 47 + ); 48 + } 49 + 50 + if (method === "private_key_jwt") { 51 + // Invalid client configuration. This should not happen as 52 + // "validateClientMetadata" already check this. 53 + if (!keyset) throw new Error("A keyset is required for private_key_jwt"); 54 + 55 + const alg = supportedAlgs(serverMetadata); 56 + 57 + // @NOTE we can't use `keyset.findPrivateKey` here because we can't enforce 58 + // that the returned key contains a "kid". The following implementation is 59 + // more robust against keysets containing keys without a "kid" property. 60 + for (const key of keyset.list({ alg, usage: "sign" })) { 61 + // Return the first key from the key set that matches the server's 62 + // supported algorithms. 63 + if (key.kid) return { method: "private_key_jwt", kid: key.kid }; 64 + } 65 + 66 + throw new Error( 67 + alg.includes(FALLBACK_ALG) 68 + ? `Client authentication method "${method}" requires at least one "${FALLBACK_ALG}" signing key with a "kid" property` 69 + : // AS is not compliant with the ATproto OAuth spec. 70 + `Authorization server requires "${method}" authentication method, but does not support "${FALLBACK_ALG}" algorithm.`, 71 + ); 72 + } 73 + 74 + if (method === "none") { 75 + return { method: "none" }; 76 + } 77 + 78 + throw new Error( 79 + `The ATProto OAuth spec requires that client use either "none" or "private_key_jwt" authentication method.` + 80 + (method === "client_secret_basic" 81 + ? ' You might want to explicitly set "token_endpoint_auth_method" to one of those values in the client metadata document.' 82 + : ` You set "${method}" which is not allowed.`), 83 + ); 84 + }
+116
apps/api/src/auth/oauth-client.ts
··· 1 + import { 2 + type AuthorizeOptions, 3 + NodeOAuthClient, 4 + type NodeOAuthClientOptions, 5 + type OAuthAuthorizationRequestParameters, 6 + } from "@atproto/oauth-client-node"; 7 + import { FALLBACK_ALG, negotiateClientAuthMethod } from "./oauth-client-auth"; 8 + 9 + export class CustomOAuthClient extends NodeOAuthClient { 10 + constructor(options: NodeOAuthClientOptions) { 11 + super(options); 12 + } 13 + 14 + async authorize( 15 + input: string, 16 + { signal, ...options }: AuthorizeOptions = {}, 17 + ): Promise<URL> { 18 + const redirectUri = 19 + options?.redirect_uri ?? this.clientMetadata.redirect_uris[0]; 20 + if (!this.clientMetadata.redirect_uris.includes(redirectUri)) { 21 + // The server will enforce this, but let's catch it early 22 + throw new TypeError("Invalid redirect_uri"); 23 + } 24 + 25 + const { identityInfo, metadata } = await this.oauthResolver.resolve(input, { 26 + signal, 27 + }); 28 + 29 + const pkce = await this.runtime.generatePKCE(); 30 + const dpopKey = await this.runtime.generateKey( 31 + metadata.dpop_signing_alg_values_supported || [FALLBACK_ALG], 32 + ); 33 + 34 + const authMethod = negotiateClientAuthMethod( 35 + metadata, 36 + this.clientMetadata, 37 + this.keyset, 38 + ); 39 + const state = await this.runtime.generateNonce(); 40 + 41 + await this.stateStore.set(state, { 42 + iss: metadata.issuer, 43 + authMethod, 44 + dpopKey, 45 + verifier: pkce.verifier, 46 + appState: options?.state, 47 + }); 48 + 49 + const parameters: OAuthAuthorizationRequestParameters = { 50 + ...options, 51 + 52 + client_id: this.clientMetadata.client_id, 53 + redirect_uri: redirectUri, 54 + code_challenge: pkce.challenge, 55 + code_challenge_method: pkce.method, 56 + state, 57 + login_hint: identityInfo && !options.prompt ? input : undefined, 58 + response_mode: this.responseMode, 59 + response_type: "code" as const, 60 + scope: options?.scope ?? this.clientMetadata.scope, 61 + }; 62 + 63 + const authorizationUrl = new URL(metadata.authorization_endpoint); 64 + 65 + // Since the user will be redirected to the authorization_endpoint url using 66 + // a browser, we need to make sure that the url is valid. 67 + if ( 68 + authorizationUrl.protocol !== "https:" && 69 + authorizationUrl.protocol !== "http:" 70 + ) { 71 + throw new TypeError( 72 + `Invalid authorization endpoint protocol: ${authorizationUrl.protocol}`, 73 + ); 74 + } 75 + 76 + if (metadata.pushed_authorization_request_endpoint) { 77 + const server = await this.serverFactory.fromMetadata( 78 + metadata, 79 + authMethod, 80 + dpopKey, 81 + ); 82 + const parResponse = await server.request( 83 + "pushed_authorization_request", 84 + parameters, 85 + ); 86 + 87 + authorizationUrl.searchParams.set( 88 + "client_id", 89 + this.clientMetadata.client_id, 90 + ); 91 + authorizationUrl.searchParams.set("request_uri", parResponse.request_uri); 92 + return authorizationUrl; 93 + } else if (metadata.require_pushed_authorization_requests) { 94 + throw new Error( 95 + "Server requires pushed authorization requests (PAR) but no PAR endpoint is available", 96 + ); 97 + } else { 98 + for (const [key, value] of Object.entries(parameters)) { 99 + if (value) authorizationUrl.searchParams.set(key, String(value)); 100 + } 101 + 102 + // Length of the URL that will be sent to the server 103 + const urlLength = 104 + authorizationUrl.pathname.length + authorizationUrl.search.length; 105 + if (urlLength < 2048) { 106 + return authorizationUrl; 107 + } else if (!metadata.pushed_authorization_request_endpoint) { 108 + throw new Error("Login URL too long"); 109 + } 110 + } 111 + 112 + throw new Error( 113 + "Server does not support pushed authorization requests (PAR)", 114 + ); 115 + } 116 + }
+41 -3
apps/api/src/bsky/app.ts
··· 17 17 import spotifyAccounts from "schema/spotify-accounts"; 18 18 import spotifyTokens from "schema/spotify-tokens"; 19 19 import users from "schema/users"; 20 + import { SCOPES } from "auth/client"; 20 21 21 22 const app = new Hono(); 22 23 ··· 31 32 const url = await ctx.oauthClient.authorize( 32 33 prompt ? "tsiry.selfhosted.social" : handle, 33 34 { 34 - scope: "atproto transition:generic", 35 - // @ts-ignore: allow custom prompt param 35 + scope: SCOPES.join(" "), 36 + // @ts-expect-error: allow custom prompt param 36 37 prompt, 37 38 }, 38 39 ); ··· 103 104 } 104 105 105 106 const url = await ctx.oauthClient.authorize(handle, { 106 - scope: "atproto transition:generic", 107 + scope: SCOPES.join(" "), 107 108 }); 108 109 109 110 if (cli) { ··· 320 321 321 322 return c.json({ token }); 322 323 }); 324 + 325 + app.get("/oauth-client-metadata.json", (c) => 326 + c.json(ctx.oauthClient.clientMetadata), 327 + ); 328 + 329 + app.get("/jwks.json", (c) => 330 + c.json({ 331 + keys: [ 332 + { 333 + kty: "EC", 334 + use: "sig", 335 + alg: "ES256", 336 + kid: "2dfa3fd9-57b3-4738-ac27-9e6dadec13b7", 337 + crv: "P-256", 338 + x: "V_00KDnoEPsNqbt0y2Ke8v27Mv9WP70JylDUD5rvIek", 339 + y: "HAyjaQeA2DU6wjZO0ggTadUS6ij1rmiYTxzmWeBKfRc", 340 + }, 341 + { 342 + kty: "EC", 343 + use: "sig", 344 + alg: "ES256", 345 + kid: "5e816ff2-6bff-4177-b1c0-67ad3cd3e7cd", 346 + crv: "P-256", 347 + x: "YwEY5NsoYQVB_G7xPYMl9sUtxRbcPFNffnZcTS5nbPQ", 348 + y: "5n5mybPvISyYAnRv1Ii1geqKfXv2GA8p9Xemwx2a8CM", 349 + }, 350 + { 351 + kty: "EC", 352 + use: "sig", 353 + kid: "a1067a48-a54a-43a0-9758-4d55b51fdd8b", 354 + crv: "P-256", 355 + x: "yq17Nd2DGcjP1i9I0NN3RBmgSbLQUZOtG6ec5GaqzmU", 356 + y: "ieIU9mcfaZwAW5b3WgJkIRgddymG_ckcZ0n1XjbEIvc", 357 + }, 358 + ], 359 + }), 360 + ); 323 361 324 362 export default app;
+1 -53
apps/app-proxy/src/index.ts
··· 11 11 * Learn more at https://developers.cloudflare.com/workers/ 12 12 */ 13 13 14 - const metadata = { 15 - redirect_uris: ['https://rocksky.app/oauth/callback'], 16 - response_types: ['code'], 17 - grant_types: ['authorization_code', 'refresh_token'], 18 - scope: 'atproto transition:generic', 19 - token_endpoint_auth_method: 'none', 20 - application_type: 'web', 21 - client_id: 'https://rocksky.app/oauth-client-metadata.json', 22 - client_name: 'Rocksky', 23 - client_uri: 'https://rocksky.app', 24 - dpop_bound_access_tokens: true, 25 - }; 26 - 27 - const jwks = { 28 - keys: [ 29 - { 30 - kty: 'EC', 31 - use: 'sig', 32 - alg: 'ES256', 33 - kid: '2dfa3fd9-57b3-4738-ac27-9e6dadec13b7', 34 - crv: 'P-256', 35 - x: 'V_00KDnoEPsNqbt0y2Ke8v27Mv9WP70JylDUD5rvIek', 36 - y: 'HAyjaQeA2DU6wjZO0ggTadUS6ij1rmiYTxzmWeBKfRc', 37 - }, 38 - { 39 - kty: 'EC', 40 - use: 'sig', 41 - alg: 'ES256', 42 - kid: '5e816ff2-6bff-4177-b1c0-67ad3cd3e7cd', 43 - crv: 'P-256', 44 - x: 'YwEY5NsoYQVB_G7xPYMl9sUtxRbcPFNffnZcTS5nbPQ', 45 - y: '5n5mybPvISyYAnRv1Ii1geqKfXv2GA8p9Xemwx2a8CM', 46 - }, 47 - { 48 - kty: 'EC', 49 - use: 'sig', 50 - kid: 'a1067a48-a54a-43a0-9758-4d55b51fdd8b', 51 - crv: 'P-256', 52 - x: 'yq17Nd2DGcjP1i9I0NN3RBmgSbLQUZOtG6ec5GaqzmU', 53 - y: 'ieIU9mcfaZwAW5b3WgJkIRgddymG_ckcZ0n1XjbEIvc', 54 - }, 55 - ], 56 - }; 57 - 58 14 export default { 59 15 async fetch(request, env, ctx): Promise<Response> { 60 16 const url = new URL(request.url); 61 17 let redirectToApi = false; 62 18 63 - const API_ROUTES = ['/login', '/profile', '/token', '/now-playing', '/ws']; 19 + const API_ROUTES = ['/login', '/profile', '/token', '/now-playing', '/ws', '/oauth-client-metadata.json', '/jwks.json']; 64 20 65 21 console.log('Request URL:', url.pathname, url.pathname === '/client-metadata.json'); 66 - 67 - if (url.pathname === '/oauth-client-metadata.json') { 68 - return Response.json(metadata); 69 - } 70 - 71 - if (url.pathname === '/jwks.json') { 72 - return Response.json(jwks); 73 - } 74 22 75 23 if ( 76 24 API_ROUTES.includes(url.pathname) ||
+38 -24
bun.lock
··· 17 17 "@atproto/api": "^0.13.31", 18 18 "@atproto/common": "^0.4.6", 19 19 "@atproto/identity": "^0.4.5", 20 - "@atproto/jwk-jose": "0.1.5", 20 + "@atproto/jwk-jose": "0.1.11", 21 21 "@atproto/lex-cli": "^0.5.6", 22 22 "@atproto/lexicon": "^0.4.5", 23 - "@atproto/oauth-client-node": "0.2.14", 23 + "@atproto/oauth-client-node": "0.3.16", 24 24 "@atproto/sync": "^0.1.11", 25 25 "@atproto/syntax": "^0.3.1", 26 26 "@atproto/xrpc-server": "^0.7.8", ··· 372 372 373 373 "@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=="], 374 374 375 - "@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=="], 375 + "@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=="], 376 376 377 - "@atproto-labs/fetch": ["@atproto-labs/fetch@0.2.2", "", { "dependencies": { "@atproto-labs/pipe": "0.1.0" } }, "sha512-QyafkedbFeVaN20DYUpnY2hcArYxjdThPXbYMqOSoZhcvkrUqaw4xDND4wZB5TBD9cq2yqe9V6mcw9P4XQKQuQ=="], 377 + "@atproto-labs/fetch": ["@atproto-labs/fetch@0.2.3", "", { "dependencies": { "@atproto-labs/pipe": "0.1.1" } }, "sha512-NZtbJOCbxKUFRFKMpamT38PUQMY0hX0p7TG5AEYOPhZKZEP7dHZ1K2s1aB8MdVH0qxmqX7nQleNrrvLf09Zfdw=="], 378 378 379 - "@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=="], 379 + "@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=="], 380 380 381 - "@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=="], 381 + "@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=="], 382 382 383 - "@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=="], 383 + "@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=="], 384 384 385 - "@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=="], 385 + "@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=="], 386 386 387 - "@atproto-labs/pipe": ["@atproto-labs/pipe@0.1.0", "", {}, "sha512-ghOqHFyJlQVFPESzlVHjKroP0tPzbmG5Jms0dNI9yLDEfL8xp4OFPWLX4f6T8mRq69wWs4nIDM3sSsFbFqLa1w=="], 387 + "@atproto-labs/pipe": ["@atproto-labs/pipe@0.1.1", "", {}, "sha512-hdNw2oUs2B6BN1lp+32pF7cp8EMKuIN5Qok2Vvv/aOpG/3tNSJ9YkvfI0k6Zd188LeDDYRUpYpxcoFIcGH/FNg=="], 388 388 389 - "@atproto-labs/simple-store": ["@atproto-labs/simple-store@0.1.2", "", {}, "sha512-9vTNvyPPBs44tKVFht16wGlilW8u4wpEtKwLkWbuNEh3h9TTQ8zjVhEoGZh/v73G4Otr9JUOSIq+/5+8OZD2mQ=="], 389 + "@atproto-labs/simple-store": ["@atproto-labs/simple-store@0.3.0", "", {}, "sha512-nOb6ONKBRJHRlukW1sVawUkBqReLlLx6hT35VS3imaNPwiXDxLnTK7lxw3Lrl9k5yugSBDQAkZAq3MPTEFSUBQ=="], 390 390 391 - "@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=="], 391 + "@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=="], 392 392 393 393 "@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=="], 394 394 ··· 398 398 399 399 "@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=="], 400 400 401 - "@atproto/did": ["@atproto/did@0.1.5", "", { "dependencies": { "zod": "^3.23.8" } }, "sha512-8+1D08QdGE5TF0bB0vV8HLVrVZJeLNITpRTUVEoABNMRaUS7CoYSVb0+JNQDeJIVmqMjOL8dOjvCUDkp3gEaGQ=="], 401 + "@atproto/did": ["@atproto/did@0.3.0", "", { "dependencies": { "zod": "^3.23.8" } }, "sha512-raUPzUGegtW/6OxwCmM8bhZvuIMzxG5t9oWsth6Tp91Kb5fTnHV2h/KKNF1C82doeA4BdXCErTyg7ISwLbQkzA=="], 402 402 403 403 "@atproto/identity": ["@atproto/identity@0.4.9", "", { "dependencies": { "@atproto/common-web": "^0.4.3", "@atproto/crypto": "^0.4.4" } }, "sha512-pRYCaeaEJMZ4vQlRQYYTrF3cMiRp21n/k/pUT1o7dgKby56zuLErDmFXkbKfKWPf7SgWRgamSaNmsGLqAOD7lQ=="], 404 404 405 - "@atproto/jwk": ["@atproto/jwk@0.1.4", "", { "dependencies": { "multiformats": "^9.9.0", "zod": "^3.23.8" } }, "sha512-dSRuEi0FbxL5ln6hEFHp5ZW01xbQH9yJi5odZaEYpcA6beZHf/bawlU12CQy/CDsbC3FxSqrBw7Q2t7mvdSBqw=="], 405 + "@atproto/jwk": ["@atproto/jwk@0.6.0", "", { "dependencies": { "multiformats": "^9.9.0", "zod": "^3.23.8" } }, "sha512-bDoJPvt7TrQVi/rBfBrSSpGykhtIriKxeYCYQTiPRKFfyRhbgpElF0wPXADjIswnbzZdOwbY63az4E/CFVT3Tw=="], 406 406 407 - "@atproto/jwk-jose": ["@atproto/jwk-jose@0.1.5", "", { "dependencies": { "@atproto/jwk": "0.1.4", "jose": "^5.2.0" } }, "sha512-piYZ3ohKhRiGlD6/bZCV/Ed3lIi7CVd6txbofEHik22EkYWK0nWKoEriCUSTssSylwFzeOq2r31Ut16WcJoghw=="], 407 + "@atproto/jwk-jose": ["@atproto/jwk-jose@0.1.11", "", { "dependencies": { "@atproto/jwk": "0.6.0", "jose": "^5.2.0" } }, "sha512-i4Fnr2sTBYmMmHXl7NJh8GrCH+tDQEVWrcDMDnV5DjJfkgT17wIqvojIw9SNbSL4Uf0OtfEv6AgG0A+mgh8b5Q=="], 408 408 409 - "@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=="], 409 + "@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=="], 410 410 411 411 "@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=="], 412 412 413 + "@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=="], 414 + 415 + "@atproto/lex-json": ["@atproto/lex-json@0.0.9", "", { "dependencies": { "@atproto/lex-data": "0.0.9", "tslib": "^2.8.1" } }, "sha512-Q2v1EVZcnd+ndyZj1r2UlGikA7q6It24CFPLbxokcf5Ba4RBupH8IkkQX7mqUDSRWPgQdmZYIdW9wUln+MKDqw=="], 416 + 413 417 "@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=="], 414 418 415 - "@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=="], 419 + "@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=="], 416 420 417 - "@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=="], 421 + "@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=="], 418 422 419 - "@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=="], 423 + "@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=="], 420 424 421 425 "@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=="], 422 426 ··· 2464 2468 2465 2469 "proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="], 2466 2470 2467 - "psl": ["psl@1.15.0", "", { "dependencies": { "punycode": "^2.3.1" } }, "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w=="], 2468 - 2469 2471 "pump": ["pump@3.0.3", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA=="], 2470 2472 2471 2473 "punycode": ["punycode@1.4.1", "", {}, "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ=="], ··· 2834 2836 2835 2837 "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=="], 2836 2838 2839 + "unicode-segmenter": ["unicode-segmenter@0.14.5", "", {}, "sha512-jHGmj2LUuqDcX3hqY12Ql+uhUTn8huuxNZGq7GvtF6bSybzH3aFgedYu/KTzQStEgt1Ra2F3HxadNXsNjb3m3g=="], 2840 + 2837 2841 "universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="], 2838 2842 2839 2843 "unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="], ··· 2943 2947 "@atproto-labs/fetch-node/ipaddr.js": ["ipaddr.js@2.2.0", "", {}, "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA=="], 2944 2948 2945 2949 "@atproto-labs/fetch-node/undici": ["undici@6.22.0", "", {}, "sha512-hU/10obOIu62MGYjdskASR3CUAiYaFTtC9Pa6vHyf//mAipSvSQg6od2CnJswq7fvzNS3zJhxoRkgNVaHurWKw=="], 2946 - 2947 - "@atproto-labs/identity-resolver/@atproto/syntax": ["@atproto/syntax@0.4.0", "", {}, "sha512-b9y5ceHS8YKOfP3mdKmwAx5yVj9294UN7FG2XzP6V5aKUdFazEYRnR9m5n5ZQFKa3GNvz7de9guZCJ/sUTcOAA=="], 2948 2950 2949 2951 "@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=="], 2950 2952 ··· 2958 2960 2959 2961 "@atproto/lexicon/@atproto/syntax": ["@atproto/syntax@0.4.0", "", {}, "sha512-b9y5ceHS8YKOfP3mdKmwAx5yVj9294UN7FG2XzP6V5aKUdFazEYRnR9m5n5ZQFKa3GNvz7de9guZCJ/sUTcOAA=="], 2960 2962 2963 + "@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=="], 2964 + 2961 2965 "@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=="], 2962 2966 2963 2967 "@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=="], ··· 3101 3105 "@poppinss/colors/kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="], 3102 3106 3103 3107 "@poppinss/dumper/supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="], 3108 + 3109 + "@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=="], 3104 3110 3105 3111 "@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=="], 3106 3112 ··· 3264 3270 3265 3271 "prop-types-extra/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="], 3266 3272 3267 - "psl/punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], 3268 - 3269 3273 "raw-body/iconv-lite": ["iconv-lite@0.7.0", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ=="], 3270 3274 3271 3275 "rc/strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="], ··· 3355 3359 "@atproto/common/@ipld/dag-cbor/cborg": ["cborg@1.10.2", "", { "bin": { "cborg": "cli.js" } }, "sha512-b3tFPA9pUr2zCUiCfRd2+wok2/LBSNUMKOuRRok+WlvvAgEt/PlbgPTsZUcwCOs53IJvLgTp0eotwtosE6njug=="], 3356 3360 3357 3361 "@atproto/lex-cli/chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], 3362 + 3363 + "@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=="], 3358 3364 3359 3365 "@atproto/repo/@ipld/dag-cbor/cborg": ["cborg@1.10.2", "", { "bin": { "cborg": "cli.js" } }, "sha512-b3tFPA9pUr2zCUiCfRd2+wok2/LBSNUMKOuRRok+WlvvAgEt/PlbgPTsZUcwCOs53IJvLgTp0eotwtosE6njug=="], 3360 3366 ··· 3500 3506 3501 3507 "@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], 3502 3508 3509 + "@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=="], 3510 + 3511 + "@rocksky/cli/@atproto/jwk-jose/jose": ["jose@5.10.0", "", {}, "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg=="], 3512 + 3503 3513 "@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=="], 3504 3514 3505 3515 "@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=="], ··· 3725 3735 "wrangler/miniflare/youch": ["youch@3.3.4", "", { "dependencies": { "cookie": "^0.7.1", "mustache": "^4.2.0", "stacktracey": "^2.1.8" } }, "sha512-UeVBXie8cA35DS6+nBkls68xaBBXCye0CNznrhszZjTbRVnJKQuNsyLKBTTL4ln1o1rh2PKtv35twV7irj5SEg=="], 3726 3736 3727 3737 "wrangler/miniflare/zod": ["zod@3.22.3", "", {}, "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug=="], 3738 + 3739 + "@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=="], 3740 + 3741 + "@atproto/oauth-client/@atproto/xrpc/@atproto/lexicon/@atproto/syntax": ["@atproto/syntax@0.4.3", "", { "dependencies": { "tslib": "^2.8.1" } }, "sha512-YoZUz40YAJr5nPwvCDWgodEOlt5IftZqPJvA0JDWjuZKD8yXddTwSzXSaKQAzGOpuM+/A3uXRtPzJJqlScc+iA=="], 3728 3742 3729 3743 "@atproto/sync/@atproto/repo/@ipld/dag-cbor/cborg": ["cborg@1.10.2", "", { "bin": { "cborg": "cli.js" } }, "sha512-b3tFPA9pUr2zCUiCfRd2+wok2/LBSNUMKOuRRok+WlvvAgEt/PlbgPTsZUcwCOs53IJvLgTp0eotwtosE6njug=="], 3730 3744
+25
tools/.zed/settings.json
··· 1 + { 2 + "lsp": { 3 + "deno": { 4 + "settings": { 5 + "deno": { 6 + "enable": true, 7 + "unstable": false, 8 + "lint": true, 9 + "cache": null 10 + } 11 + } 12 + } 13 + }, 14 + "languages": { 15 + "TypeScript": { 16 + "language_servers": ["deno", "!typescript-language-server"] 17 + }, 18 + "TSX": { 19 + "language_servers": ["deno", "!typescript-language-server"] 20 + }, 21 + "JavaScript": { 22 + "language_servers": ["deno", "!typescript-language-server"] 23 + } 24 + } 25 + }
+3 -2
tools/deno.json
··· 1 1 { 2 2 "tasks": { 3 - "cron": "deno run cron.ts" 3 + "cron": "deno run cron.ts", 4 + "local-proxy": "deno run -A local-proxy.ts" 4 5 }, 5 6 "imports": { 6 7 "@std/assert": "jsr:@std/assert@1", 7 8 "chalk": "npm:chalk@^5.6.2" 8 9 } 9 - } 10 + }
+3 -3
tools/deno.lock
··· 1 1 { 2 2 "version": "5", 3 3 "specifiers": { 4 - "jsr:@std/assert@1": "1.0.15", 4 + "jsr:@std/assert@1": "1.0.16", 5 5 "jsr:@std/internal@^1.0.12": "1.0.12", 6 6 "npm:chalk@^5.6.2": "5.6.2" 7 7 }, 8 8 "jsr": { 9 - "@std/assert@1.0.15": { 10 - "integrity": "d64018e951dbdfab9777335ecdb000c0b4e3df036984083be219ce5941e4703b", 9 + "@std/assert@1.0.16": { 10 + "integrity": "6a7272ed1eaa77defe76e5ff63ca705d9c495077e2d5fd0126d2b53fc5bd6532", 11 11 "dependencies": [ 12 12 "jsr:@std/internal" 13 13 ]
+90
tools/local-proxy.ts
··· 1 + const PORT = parseInt(Deno.env.get("PORT") || "8081"); 2 + 3 + console.log(`HTTP Proxy Server is running on http://localhost:${PORT}`); 4 + 5 + Deno.serve({ port: PORT }, async (req) => { 6 + const url = new URL(req.url); 7 + 8 + try { 9 + let target: URL; 10 + 11 + if ( 12 + url.pathname.startsWith("/xrpc") || 13 + url.pathname.startsWith("/login") || 14 + url.pathname.startsWith("/oauth/callback") || 15 + url.pathname.startsWith("/users") || 16 + url.pathname.startsWith("/albums") || 17 + url.pathname.startsWith("/artists") || 18 + url.pathname.startsWith("/tracks") || 19 + url.pathname.startsWith("/scrobbles") || 20 + url.pathname.startsWith("/likes") || 21 + url.pathname.startsWith("/spotify") || 22 + url.pathname.startsWith("/dropbox/oauth/callback") || 23 + url.pathname.startsWith("/googledrive/oauth/callback") || 24 + url.pathname.startsWith("/dropbox/files") || 25 + url.pathname.startsWith("/dropbox/file") || 26 + url.pathname.startsWith("/googledrive/files") || 27 + url.pathname.startsWith("/dropbox/login") || 28 + url.pathname.startsWith("/googledrive/login") || 29 + url.pathname.startsWith("/dropbox/join") || 30 + url.pathname.startsWith("/googledrive/join") || 31 + url.pathname.startsWith("/search") || 32 + url.pathname.startsWith("/public/scrobbles") 33 + ) { 34 + // API requests 35 + target = new URL(url); 36 + target.host = "localhost"; 37 + target.port = "4004"; 38 + } else { 39 + // Vite frontend requests 40 + target = new URL(url); 41 + target.host = "localhost"; 42 + target.port = "5174"; 43 + } 44 + 45 + // Handle WebSocket connections 46 + if (req.headers.get("upgrade") === "websocket") { 47 + const { socket, response } = Deno.upgradeWebSocket(req); 48 + const wsUrl = `ws://${target.host}${target.pathname}${target.search}`; 49 + const ws = new WebSocket(wsUrl); 50 + 51 + ws.onopen = () => { 52 + console.log("WebSocket connection established"); 53 + }; 54 + ws.onmessage = (event) => { 55 + socket.send(event.data); 56 + }; 57 + ws.onclose = () => { 58 + socket.close(); 59 + }; 60 + ws.onerror = (event) => { 61 + console.error("WebSocket error:", event); 62 + socket.close(); 63 + }; 64 + socket.onmessage = (event) => { 65 + ws.send(event.data); 66 + }; 67 + socket.onclose = () => { 68 + ws.close(); 69 + }; 70 + return response; 71 + } 72 + 73 + // Proxy HTTP requests 74 + const proxyRequest = new Request(target.toString(), { 75 + method: req.method, 76 + headers: req.headers, 77 + body: req.body, 78 + }); 79 + 80 + const response = await fetch(proxyRequest); 81 + 82 + return new Response(response.body, { 83 + status: response.status, 84 + headers: response.headers, 85 + }); 86 + } catch (error) { 87 + console.error("Proxy error:", error); 88 + return new Response("Failed to fetch the target URL", { status: 500 }); 89 + } 90 + });