A decentralized music tracking and discovery platform built on AT Protocol 🎵 rocksky.app
spotify atproto lastfm musicbrainz scrobbling listenbrainz
97
fork

Configure Feed

Select the types of activity you want to include in your feed.

Refactor OAuth client and add local proxy

Bump @atproto deps (jwk-jose, oauth-client-node). Extract OAuth
client and auth negotiation into dedicated modules and simplify
client.ts. Make app-proxy forward oauth-client-metadata.json and
jwks.json instead of serving hardcoded values. Add a Deno local
proxy script and task plus editor settings for local development.

+420 -195
+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 { 2 + ClientMetadata, 3 + Keyset, 4 + OAuthAuthorizationServerMetadata, 5 + } from "@atproto/oauth-client-node"; 6 + 7 + import { 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 + AuthorizeOptions, 3 + NodeOAuthClient, 4 + NodeOAuthClientOptions, 5 + 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 + }
+40 -2
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 + scope: SCOPES.join(" "), 35 36 // @ts-ignore: allow custom prompt param 36 37 prompt, 37 38 }, ··· 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 + });