@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 +1 -1
- package/src/lib/appview.ts +25 -1
- package/src/pages/xrpc/app.bsky.actor.getProfile.ts +36 -21
- package/src/pages/xrpc/app.bsky.actor.getProfiles.ts +11 -2
- package/src/pages/xrpc/app.bsky.feed.getActorFeeds.ts +25 -0
- package/src/pages/xrpc/app.bsky.feed.getFeedGenerators.ts +25 -0
- package/src/pages/xrpc/app.bsky.feed.getTimeline.ts +32 -20
package/package.json
CHANGED
package/src/lib/appview.ts
CHANGED
|
@@ -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)
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
28
|
-
|
|
31
|
+
const payload: Record<string, unknown> = { feed };
|
|
32
|
+
if (nextCursor) payload.cursor = nextCursor;
|
|
29
33
|
|
|
30
|
-
|
|
31
|
-
|
|
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
|
}
|