Open lovemyliwu opened 5 days ago
some detail clue related to app view api call
config.cdnUrl => process.env.BSKY_CDN_URL || process.env.BSKY_IMG_URI_ENDPOINT => https://cdn.bsky.app/img/
config.videoPlaylistUrlPattern => process.env.BSKY_VIDEO_PLAYLIST_URL_PATTERN => https://video.bsky.app/watch/%s/%s/playlist.m3u8
config.videoThumbnailUrlPattern => process.env.BSKY_VIDEO_THUMBNAIL_URL_PATTERN => https://video.cdn.bsky.app/hls/%s/%s/thumbnail.jpg
!https://github.com/bluesky-social/atproto/blob/main/packages/bsky/src/index.ts#L159
index
=> call lexicon create server => call create xrpc server with schema define as variable => define namespace(it is a functino to define handler to xrpc server)
=> call API with lexicon server => call detail api init => call lexicon server namespace(finished define handler to xrpc server)
!https://github.com/bluesky-social/atproto/blob/main/packages/xrpc-server/src/server.ts#L113
xrpc-server-define-method
=> call add method => call add route => define middlewares variable => call create handler as handler variable
=> call express get or post defne method with middlewares variable and handler variable
!https://github.com/bluesky-social/atproto/blob/main/packages/xrpc-server/src/server.ts#L236
xrpc-server-define-create-handler
=> define validator for input and output => return dynamic defined function
!https://github.com/bluesky-social/atproto/blob/main/packages/xrpc-server/src/server.ts#L257
express-get-or-post-define-handler
=> decode query params => validate input => define request context => check rate limit
=> call detail api handler => validate output => encode output
=> call express response json method
detail api handler render url by call Views.post
from presentation
function.
the Views.embed
will call imgUriBuilder
or videoUriBuilder
to generate url from Views.post
function.
at viewer level, it can't see the express reqeust object because it is a non-route function, and since the call chain is very deepth, we should not pass the request to viewer.
I asked AI how to get request object from a non-route function, it said:
在Node.js的Express框架中,如果你想在非路由处理函数中获取当前的request header,而不显式地传递req
对象,可以使用AsyncLocalStorage
来实现。AsyncLocalStorage
是Node.js的一个内置模块,它提供了一种机制来保持异步操作的上下文。
以下是如何使用AsyncLocalStorage
来在非路由处理函数中获取当前的request header的步骤:
创建一个AsyncLocalStorage
实例:
在你的应用中创建一个AsyncLocalStorage
的实例,这将用于存储和访问请求上下文。
const { AsyncLocalStorage } = require('async_hooks');
const asyncLocalStorage = new AsyncLocalStorage();
在请求处理中设置上下文:
在Express的中间件中,使用asyncLocalStorage.run()
方法来设置当前请求的上下文。你可以将请求对象或其他需要在请求生命周期内访问的数据存储在上下文中。
app.use((req, res, next) => {
asyncLocalStorage.run(req, () => {
next();
});
});
在非路由处理函数中访问上下文:
在任何异步操作中,你可以使用asyncLocalStorage.getStore()
来获取当前请求的上下文,从而访问request header。
function someAsyncFunction() {
const req = asyncLocalStorage.getStore();
if (req) {
// 访问请求头
const headerValue = req.headers['some-header'];
console.log(headerValue);
}
}
确保上下文的正确性:
由于Node.js的事件循环和异步操作的特性,确保在异步操作完成之前上下文不会被破坏是很重要的。AsyncLocalStorage
会自动处理这些情况,确保每个异步操作都能访问到正确的上下文。
请注意,AsyncLocalStorage
是在Node.js v13.10.0和v12.17.0中引入的,因此你需要确保你的Node.js版本至少是这些版本之一。此外,使用AsyncLocalStorage
可能会带来一定的性能开销,但在大多数情况下,这种开销是可以接受的。
currently, I will proxy the bluesky api server, after research, these api contains CDN url
lex:app.bsky.feed.defs#postView
=> lex:app.bsky.feed.defs#feedViewPost
=> app.bsky.feed.getActorLikes
=> app.bsky.feed.getAuthorFeed
=> app.bsky.feed.getFeed
=> app.bsky.feed.getListFeed
=> app.bsky.feed.getTimeline
=> lex:app.bsky.feed.defs#replyRef
=> lex:app.bsky.feed.defs#feedViewPost
=> lex:app.bsky.feed.defs#threadViewPost
=> lex:app.bsky.feed.defs#threadViewPost
=> app.bsky.feed.getPostThread
=> app.bsky.feed.getPosts
=> app.bsky.feed.getQuotes
=> app.bsky.feed.searchPosts
for somebody want to workaround like me, create a cf worker to replace bluesky cdn url to yourself CDN, then update the pds.env
file field PDS_BSKY_APP_VIEW_URL
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request))
})
function needModify(pathname) {
let api_names = [
'app.bsky.feed.getActorLikes',
'app.bsky.feed.getAuthorFeed',
'app.bsky.feed.getFeed',
'app.bsky.feed.getListFeed',
'app.bsky.feed.getTimeline',
'app.bsky.feed.getPostThread',
'app.bsky.feed.getPosts',
'app.bsky.feed.getQuotes',
'app.bsky.feed.searchPosts',
// below related to avatar
'app.bsky.actor.getProfile',
'app.bsky.actor.getProfiles',
'app.bsky.actor.getSuggestions',
'app.bsky.actor.searchActors',
'app.bsky.actor.searchActorsTypeahead',
'app.bsky.feed.getActorFeeds',
'app.bsky.feed.getFeedGenerator',
'app.bsky.feed.getFeedGenerators',
'app.bsky.feed.getLikes',
'app.bsky.feed.getRepostedBy',
'app.bsky.feed.getSuggestedFeeds',
'app.bsky.graph.getActorStarterPacks',
'app.bsky.graph.getBlocks',
'app.bsky.graph.getFollowers',
'app.bsky.graph.getFollows',
'app.bsky.graph.getKnownFollowers',
'app.bsky.graph.getList',
'app.bsky.graph.getListBlocks',
'app.bsky.graph.getListMutes',
'app.bsky.graph.getLists',
'app.bsky.graph.getMutes',
'app.bsky.graph.getStarterPack',
'app.bsky.graph.getSuggestedFollowsByActor',
'app.bsky.graph.searchStarterPacks',
'app.bsky.labeler.getServices',
'app.bsky.notification.listNotifications',
'app.bsky.unspecced.getPopularFeedGenerators',
'chat.bsky.convo.getConvo',
'chat.bsky.convo.getConvoForMembers',
'chat.bsky.convo.getLog',
'chat.bsky.convo.getMessages',
'chat.bsky.convo.listConvos',
'chat.bsky.convo.muteConvo',
'chat.bsky.convo.sendMessage',
'chat.bsky.convo.sendMessageBatch',
'chat.bsky.convo.unmuteConvo',
'chat.bsky.convo.updateRead',
'chat.bsky.moderation.getMessageContext'
]
return api_names.includes(pathname.substring('/xrpc/'.length))
}
function replaceCDN_URL(full_url) {
let url = new URL(full_url)
url.pathname = `/${url.host}${url.pathname}`
url.host = 'YOUR-CDN-HOST'
return url.toString()
}
async function handleRequest(request) {
const url = new URL(request.url)
url.hostname = 'api.bsky.app'
const newRequest = new Request(url, request)
let response = await fetch(newRequest)
const responseClone = response.clone();
// 检查响应内容类型是否为JSON
if (needModify(url.pathname) && response.headers.get('content-type').includes('application/json')) {
// 读取响应文本
const json = await responseClone.json();
let cdn_img_url = 'https://cdn.bsky.app/img/';
let cdn_video_playlist_url = 'https://video.bsky.app/watch/';
let cdn_video_thumbnail_url = 'https://video.cdn.bsky.app/hls/'
// 使用递归函数遍历并替换JSON中的特定字符串
function replaceInJson(obj) {
Object.keys(obj).forEach(key => {
if (typeof obj[key] === 'object' && obj[key] !== null) {
replaceInJson(obj[key]);
} else if (typeof obj[key] === 'string' && (obj[key].startsWith(cdn_img_url) || obj[key].startsWith(cdn_video_playlist_url) || obj[key].startsWith(cdn_video_thumbnail_url))) {
obj[key] = replaceCDN_URL(obj[key]);
}
});
}
// 替换JSON中的字符串
replaceInJson(json);
// 将修改后的JSON转换为字符串
const modifiedJsonString = JSON.stringify(json);
// 返回新的响应,保持原始响应的headers
return new Response(modifiedJsonString, {
status: response.status,
headers: response.headers
});
} else {
// 如果不是JSON响应,直接返回原始响应
return response;
}
}
Is your feature request related to a problem? Please describe. I saw
PDS_BSKY_APP_VIEW_CDN_URL_PATTERN
can config, but only effect to local viewer, I need customize the cdn url from app view level.Describe the solution you'd like
when app view render cdn url, consider the requester customize config through header, for example
PDS-CFG-CDN-URL-PATTERN=https://thecdn.customize.com/%s/%s/%s
Describe alternatives you've considered
Additional context