@alteran/astro 0.3.6 → 0.3.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alteran/astro",
3
- "version": "0.3.6",
3
+ "version": "0.3.8",
4
4
  "description": "Astro integration for running a Cloudflare-hosted Bluesky PDS with Alteran.",
5
5
  "module": "index.js",
6
6
  "types": "index.d.ts",
@@ -350,22 +350,34 @@ export interface ProxyAppViewOptions {
350
350
  }
351
351
 
352
352
  export async function proxyAppView({ request, env, lxm, fallback }: ProxyAppViewOptions): Promise<Response> {
353
+ console.log('proxyAppView called:', { lxm, url: request.url });
354
+
353
355
  const config = getAppViewConfig(env);
354
356
  if (!config) {
357
+ console.log('proxyAppView: No appview config, using fallback');
355
358
  return fallback ? await fallback() : new Response('AppView not configured', { status: 501 });
356
359
  }
357
360
 
361
+ console.log('proxyAppView: AppView config found:', { url: config.url, did: config.did });
362
+
358
363
  const auth = await authenticateRequest(request, env);
359
- if (!auth) return unauthorized();
364
+ if (!auth) {
365
+ console.log('proxyAppView: Authentication failed');
366
+ return unauthorized();
367
+ }
360
368
 
361
369
  if (!auth.claims.sub) {
370
+ console.log('proxyAppView: No subject in auth claims');
362
371
  return new Response(JSON.stringify({ error: 'InvalidToken' }), {
363
372
  status: 401,
364
373
  headers: { 'Content-Type': 'application/json' },
365
374
  });
366
375
  }
367
376
 
377
+ console.log('proxyAppView: Authenticated as', auth.claims.sub);
378
+
368
379
  if (PROTECTED_METHODS.has(lxm)) {
380
+ console.log('proxyAppView: Method is protected, cannot proxy');
369
381
  return new Response(
370
382
  JSON.stringify({ error: 'InvalidToken', message: 'method cannot be proxied' }),
371
383
  {
@@ -377,6 +389,7 @@ export async function proxyAppView({ request, env, lxm, fallback }: ProxyAppView
377
389
 
378
390
  const scope = resolveAuthScope(auth.claims.scope);
379
391
  if (scope === TAKENDOWN_SCOPE) {
392
+ console.log('proxyAppView: Account is takendown');
380
393
  return new Response(JSON.stringify({ error: 'AccountTakendown' }), {
381
394
  status: 403,
382
395
  headers: { 'Content-Type': 'application/json' },
@@ -384,6 +397,7 @@ export async function proxyAppView({ request, env, lxm, fallback }: ProxyAppView
384
397
  }
385
398
 
386
399
  if (!PRIVILEGED_SCOPES.has(scope) && PRIVILEGED_METHODS.has(lxm)) {
400
+ console.log('proxyAppView: Insufficient privileges for method');
387
401
  return new Response(JSON.stringify({ error: 'InvalidToken' }), {
388
402
  status: 401,
389
403
  headers: { 'Content-Type': 'application/json' },
@@ -393,6 +407,7 @@ export async function proxyAppView({ request, env, lxm, fallback }: ProxyAppView
393
407
  let target: ProxyTarget = { did: config.did, url: config.url };
394
408
  const proxyHeader = request.headers.get('atproto-proxy');
395
409
  if (proxyHeader) {
410
+ console.log('proxyAppView: Resolving proxy header:', proxyHeader);
396
411
  try {
397
412
  target = await resolveProxyTarget(env, proxyHeader, config);
398
413
  } catch (error) {
@@ -414,6 +429,8 @@ export async function proxyAppView({ request, env, lxm, fallback }: ProxyAppView
414
429
  upstreamUrl.search = originalUrl.search;
415
430
  upstreamUrl.hash = '';
416
431
 
432
+ console.log('proxyAppView: Proxying to', upstreamUrl.toString());
433
+
417
434
  const headers = new Headers();
418
435
  for (const header of FORWARDED_HEADERS) {
419
436
  const value = request.headers.get(header);
@@ -422,10 +439,13 @@ export async function proxyAppView({ request, env, lxm, fallback }: ProxyAppView
422
439
 
423
440
  let serviceJwt: string;
424
441
  try {
442
+ console.log('proxyAppView: Creating service JWT for', { iss: auth.claims.sub, aud: target.did, lxm });
425
443
  serviceJwt = await createServiceJwt(env, auth.claims.sub, target.did, lxm);
444
+ console.log('proxyAppView: Service JWT created successfully');
426
445
  } catch (error) {
427
446
  console.error('AppView service token error:', error);
428
447
  if (fallback) {
448
+ console.log('proxyAppView: Using fallback due to JWT error');
429
449
  return fallback();
430
450
  }
431
451
  return new Response(JSON.stringify({ error: 'ServiceAuthUnavailable' }), {
@@ -438,6 +458,7 @@ export async function proxyAppView({ request, env, lxm, fallback }: ProxyAppView
438
458
 
439
459
  const method = request.method.toUpperCase();
440
460
  if (method !== 'GET' && method !== 'HEAD' && method !== 'POST') {
461
+ console.log('proxyAppView: Method not allowed:', method);
441
462
  return new Response(JSON.stringify({ error: 'MethodNotAllowed' }), {
442
463
  status: 405,
443
464
  headers: {
@@ -469,7 +490,9 @@ export async function proxyAppView({ request, env, lxm, fallback }: ProxyAppView
469
490
  (init as any).duplex = 'half';
470
491
  }
471
492
 
493
+ console.log('proxyAppView: Fetching upstream');
472
494
  const upstream = await fetch(upstreamUrl.toString(), init);
495
+ console.log('proxyAppView: Upstream response:', { status: upstream.status, statusText: upstream.statusText });
473
496
 
474
497
  const responseHeaders = new Headers(upstream.headers);
475
498
  return new Response(upstream.body, {
@@ -480,6 +503,7 @@ export async function proxyAppView({ request, env, lxm, fallback }: ProxyAppView
480
503
  } catch (error) {
481
504
  console.error('AppView proxy error:', error);
482
505
  if (fallback) {
506
+ console.log('proxyAppView: Using fallback due to upstream error');
483
507
  return fallback();
484
508
  }
485
509
  return new Response(JSON.stringify({ error: 'UpstreamUnavailable' }), {
@@ -10,25 +10,40 @@ export async function GET({ locals, request }: APIContext) {
10
10
  const { env } = locals.runtime;
11
11
  if (!(await isAuthorized(request, env))) return unauthorized();
12
12
 
13
- return proxyAppView({
14
- request,
15
- env,
16
- lxm: 'app.bsky.actor.getProfile',
17
- fallback: async () => {
18
- const url = new URL(request.url);
19
- const identifier = url.searchParams.get('actor');
20
- const actor = await getPrimaryActor(env);
21
- if (!matchesPrimaryActor(identifier, actor)) {
22
- return new Response(JSON.stringify({ error: 'ProfileNotFound' }), { status: 404 });
23
- }
24
- const profile = buildProfileViewDetailed(actor, {
25
- followers: 0,
26
- follows: 0,
27
- posts: await countPosts(env),
28
- });
29
- return new Response(JSON.stringify(profile), {
30
- headers: { 'Content-Type': 'application/json' },
31
- });
32
- },
33
- });
13
+ try {
14
+ return await proxyAppView({
15
+ request,
16
+ env,
17
+ lxm: 'app.bsky.actor.getProfile',
18
+ fallback: async () => {
19
+ console.log('app.bsky.actor.getProfile: Using fallback');
20
+ const url = new URL(request.url);
21
+ const identifier = url.searchParams.get('actor');
22
+ const actor = await getPrimaryActor(env);
23
+ console.log('app.bsky.actor.getProfile: actor', { did: actor.did, handle: actor.handle, identifier });
24
+ if (!matchesPrimaryActor(identifier, actor)) {
25
+ console.log('app.bsky.actor.getProfile: identifier does not match actor');
26
+ return new Response(JSON.stringify({ error: 'ProfileNotFound' }), {
27
+ status: 404,
28
+ headers: { 'Content-Type': 'application/json' }
29
+ });
30
+ }
31
+ const profile = buildProfileViewDetailed(actor, {
32
+ followers: 0,
33
+ follows: 0,
34
+ posts: await countPosts(env),
35
+ });
36
+ console.log('app.bsky.actor.getProfile: returning profile', profile);
37
+ return new Response(JSON.stringify(profile), {
38
+ headers: { 'Content-Type': 'application/json' },
39
+ });
40
+ },
41
+ });
42
+ } catch (error) {
43
+ console.error('app.bsky.actor.getProfile error:', error);
44
+ return new Response(JSON.stringify({ error: 'InternalServerError', message: String(error) }), {
45
+ status: 500,
46
+ headers: { 'Content-Type': 'application/json' },
47
+ });
48
+ }
34
49
  }
@@ -14,13 +14,22 @@ export async function GET({ locals, request }: APIContext) {
14
14
  const { env } = locals.runtime;
15
15
  if (!(await isAuthorized(request, env))) return unauthorized();
16
16
 
17
+ // Some clients call with an empty actors list; upstream returns 400.
18
+ // For UX parity, treat missing/empty as an empty result set.
19
+ const url = new URL(request.url);
20
+ const requestedActors = url.searchParams.getAll('actors');
21
+ if (requestedActors.length === 0) {
22
+ return new Response(JSON.stringify({ profiles: [] }), {
23
+ headers: { 'Content-Type': 'application/json' },
24
+ });
25
+ }
26
+
17
27
  return proxyAppView({
18
28
  request,
19
29
  env,
20
30
  lxm: 'app.bsky.actor.getProfiles',
21
31
  fallback: async () => {
22
- const url = new URL(request.url);
23
- const actors = url.searchParams.getAll('actors');
32
+ const actors = requestedActors;
24
33
  const actor = await getPrimaryActor(env);
25
34
  const posts = await countPosts(env);
26
35
 
@@ -0,0 +1,25 @@
1
+ import type { APIContext } from 'astro';
2
+ import { proxyAppView } from '../../lib/appview';
3
+ import { isAuthorized, unauthorized } from '../../lib/auth';
4
+
5
+ export const prerender = false;
6
+
7
+ // Implements: app.bsky.feed.getActorFeeds
8
+ // Thin proxy to AppView with a safe empty fallback to satisfy clients.
9
+ export async function GET({ locals, request }: APIContext) {
10
+ const { env } = locals.runtime;
11
+ if (!(await isAuthorized(request, env))) return unauthorized();
12
+
13
+ return proxyAppView({
14
+ request,
15
+ env,
16
+ lxm: 'app.bsky.feed.getActorFeeds',
17
+ fallback: async () => {
18
+ // Minimal valid shape per lexicon when upstream unavailable
19
+ return new Response(JSON.stringify({ feeds: [] }), {
20
+ headers: { 'Content-Type': 'application/json' },
21
+ });
22
+ },
23
+ });
24
+ }
25
+
@@ -0,0 +1,25 @@
1
+ import type { APIContext } from 'astro';
2
+ import { proxyAppView } from '../../lib/appview';
3
+ import { isAuthorized, unauthorized } from '../../lib/auth';
4
+
5
+ export const prerender = false;
6
+
7
+ // Implements: app.bsky.feed.getFeedGenerators
8
+ // Thin proxy to AppView with a safe empty fallback to satisfy clients.
9
+ export async function GET({ locals, request }: APIContext) {
10
+ const { env } = locals.runtime;
11
+ if (!(await isAuthorized(request, env))) return unauthorized();
12
+
13
+ return proxyAppView({
14
+ request,
15
+ env,
16
+ lxm: 'app.bsky.feed.getFeedGenerators',
17
+ fallback: async () => {
18
+ // Minimal valid shape per lexicon when upstream unavailable
19
+ return new Response(JSON.stringify({ feeds: [] }), {
20
+ headers: { 'Content-Type': 'application/json' },
21
+ });
22
+ },
23
+ });
24
+ }
25
+
@@ -9,27 +9,39 @@ export async function GET({ locals, request }: APIContext) {
9
9
  const { env } = locals.runtime;
10
10
  if (!(await isAuthorized(request, env))) return unauthorized();
11
11
 
12
- return proxyAppView({
13
- request,
14
- env,
15
- lxm: 'app.bsky.feed.getTimeline',
16
- fallback: async () => {
17
- const url = new URL(request.url);
18
- const cursor = url.searchParams.get('cursor') ?? undefined;
19
- const limitParam = Number.parseInt(url.searchParams.get('limit') ?? '', 10);
20
- const limitInput = Number.isFinite(limitParam) ? limitParam : 50;
21
- const limit = Math.max(1, Math.min(limitInput, 100));
12
+ try {
13
+ return await proxyAppView({
14
+ request,
15
+ env,
16
+ lxm: 'app.bsky.feed.getTimeline',
17
+ fallback: async () => {
18
+ console.log('app.bsky.feed.getTimeline: Using fallback');
19
+ const url = new URL(request.url);
20
+ const cursor = url.searchParams.get('cursor') ?? undefined;
21
+ const limitParam = Number.parseInt(url.searchParams.get('limit') ?? '', 10);
22
+ const limitInput = Number.isFinite(limitParam) ? limitParam : 50;
23
+ const limit = Math.max(1, Math.min(limitInput, 100));
22
24
 
23
- const posts = await listPosts(env, limit, cursor);
24
- const feed = await buildFeedViewPosts(env, posts);
25
- const nextCursor = posts.length === limit ? String(posts[posts.length - 1].rowid) : undefined;
25
+ console.log('app.bsky.feed.getTimeline: fetching posts', { limit, cursor });
26
+ const posts = await listPosts(env, limit, cursor);
27
+ console.log('app.bsky.feed.getTimeline: found posts', posts.length);
28
+ const feed = await buildFeedViewPosts(env, posts);
29
+ const nextCursor = posts.length === limit ? String(posts[posts.length - 1].rowid) : undefined;
26
30
 
27
- const payload: Record<string, unknown> = { feed };
28
- if (nextCursor) payload.cursor = nextCursor;
31
+ const payload: Record<string, unknown> = { feed };
32
+ if (nextCursor) payload.cursor = nextCursor;
29
33
 
30
- return new Response(JSON.stringify(payload), {
31
- headers: { 'Content-Type': 'application/json' },
32
- });
33
- },
34
- });
34
+ console.log('app.bsky.feed.getTimeline: returning feed', { feedLength: feed.length, nextCursor });
35
+ return new Response(JSON.stringify(payload), {
36
+ headers: { 'Content-Type': 'application/json' },
37
+ });
38
+ },
39
+ });
40
+ } catch (error) {
41
+ console.error('app.bsky.feed.getTimeline error:', error);
42
+ return new Response(JSON.stringify({ error: 'InternalServerError', message: String(error) }), {
43
+ status: 500,
44
+ headers: { 'Content-Type': 'application/json' },
45
+ });
46
+ }
35
47
  }