Skip to content

Commit 5cfa2ba

Browse files
authored
add unpkg proxy API, update API documentation (#2376)
1 parent 243b9d7 commit 5cfa2ba

File tree

3 files changed

+161
-5
lines changed

3 files changed

+161
-5
lines changed

API.md

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ This document describes the server-side JSON API exposed by the React Native Dir
88
- [`POST /api/libraries/check`](#post-apilibrariescheck) - return metadata for a list of npm package names
99
- [`GET /api/libraries/statistic`](#get-apilibrariesstatistic) - aggregated statistics about the directory dataset
1010
- [`GET /api/library`](#get-apilibrary) - lookup one or more libraries by npm package name (optionally `check` existence only)
11+
- [`GET /api/proxy/github-funding`](#get-apiproxygithub-funding) - proxy to https://api.github.com/graphql API with baked query for fetching funding data
1112
- [`GET /api/proxy/npm-stat`](#get-apiproxynpm-stat) - proxy to https://npm-stat.com download counts API
13+
- [`GET /api/proxy/unpkg`](#get-apiproxyunpkg) - proxy to https://unpkg.com/ API with redirect handling on server-side
1214

1315
## GET /api/libraries
1416

@@ -331,6 +333,43 @@ Endpoint can optionally perform a quick `check` to return existence flag only, o
331333

332334
---
333335

336+
## GET /api/proxy/github-funding
337+
338+
Proxy to api.github.com/graphql to fetch project funding data. This endpoint uses baked query, and cannot be used to fetch other data than funding details.
339+
340+
- Method: GET
341+
- Path: `/api/proxy/github-funding`
342+
- Query parameters:
343+
- `name` - GitHub repository name (required).
344+
- `owner` - GitHub repository owner (required).
345+
346+
### Notes
347+
348+
- It is subject to the same CORS and rate limiting policies as the original api.github.com/graphql.
349+
350+
### Example
351+
352+
- GET `/api/proxy/github-funding?owner=lodev09&name=react-native-true-sheet`
353+
354+
Response:
355+
356+
```json
357+
{
358+
"fundingLinks": [
359+
{
360+
"platform": "GITHUB",
361+
"url": "https://github.com/lodev09"
362+
},
363+
{
364+
"platform": "BUY_ME_A_COFFEE",
365+
"url": "https://buymeacoffee.com/lodev09"
366+
}
367+
]
368+
}
369+
```
370+
371+
---
372+
334373
## GET /api/proxy/npm-stat
335374

336375
Proxy to npm-stat.com to fetch download counts for the last month. This endpoint is a simple proxy and does not perform any data processing.
@@ -386,3 +425,50 @@ Proxy to npm-stat.com to fetch download counts for the last month. This endpoint
386425
}
387426
}
388427
```
428+
429+
---
430+
431+
## GET /api/proxy/unpkg
432+
433+
Proxy to unpkg.com to fetch various package file content.
434+
435+
- Method: GET
436+
- Path: `/api/proxy/unpkg.com`
437+
- Query parameters:
438+
- `name` - npm package name (required).
439+
- `path` - path to wanted file from the bundle (required).
440+
441+
### Notes
442+
443+
- Redirects returned from unpkg.com are resolve server-side, before returning response.
444+
- Return will be a plain text if file content is accessible, in any other case (i.e. errors) JSON would be returned.
445+
446+
### Example
447+
448+
- GET `/api/proxy/unpkg?name=react-native-safe-area-context&path=README.md`
449+
450+
Response:
451+
452+
```mdx
453+
![safearea](https://github.com/user-attachments/assets/d951efe6-4d25-4ff6-b654-7aaf4519829b)
454+
455+
### About
456+
457+
App & Flow is a Montreal-based React Native engineering and consulting studio. We partner with the world’s top companies and are recommended by [Expo](https://expo.dev/consultants). Need a hand? Let’s build together. team@appandflow.com
458+
459+
# react-native-safe-area-context
460+
461+
[![npm](https://img.shields.io/npm/v/react-native-safe-area-context)](https://www.npmjs.com/package/react-native-safe-area-context) ![Supports Android, iOS, web, macOS and Windows](https://img.shields.io/badge/platforms-android%20%7C%20ios%20%7C%20web%20%7C%20macos%20%7C%20windows-lightgrey.svg) ![MIT License](https://img.shields.io/npm/l/react-native-safe-area-context.svg)
462+
463+
[![JavaScript tests](https://github.com/AppAndFlow/react-native-safe-area-context/workflows/JavaScript%20tests/badge.svg)](https://github.com/AppAndFlow/react-native-safe-area-context/actions/workflows/js.yml) [![iOS build](https://github.com/AppAndFlow/react-native-safe-area-context/workflows/iOS%20build/badge.svg)](https://github.com/AppAndFlow/react-native-safe-area-context/actions/workflows/ios.yml) [![Android build](https://github.com/AppAndFlow/react-native-safe-area-context/workflows/Android%20build/badge.svg)](https://github.com/AppAndFlow/react-native-safe-area-context/actions/workflows/android.yml)
464+
465+
A flexible way to handle safe area, also works on Android and Web!
466+
467+
## Documentation
468+
469+
Check out our [documentation site](https://appandflow.github.io/react-native-safe-area-context/).
470+
471+
## Contributing
472+
473+
See the [Contributing Guide](CONTRIBUTING.md)
474+
```

components/Package/MarkdownContentBox/index.tsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,15 @@ export default function MarkdownContentBox({ packageName, library, loader = fals
5252
const contentTabs = useMemo<MarkdownTab[]>(
5353
() =>
5454
[
55-
{
56-
title: 'Readme' as const,
57-
Icon: ReadmeFile,
58-
url: `https://unpkg.com/${packageName}/README.md`,
59-
},
55+
...(packageName
56+
? [
57+
{
58+
title: 'Readme' as const,
59+
Icon: ReadmeFile,
60+
url: `/api/proxy/unpkg?name=${packageName}&path=README.md`,
61+
},
62+
]
63+
: []),
6064
...(library?.github?.hasChangelog
6165
? [
6266
{

pages/api/proxy/unpkg.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { type NextApiRequest, type NextApiResponse } from 'next';
2+
3+
import { NEXT_10M_CACHE_HEADER } from '~/util/Constants';
4+
import { parseQueryParams } from '~/util/queryParams';
5+
6+
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
7+
const { name, path } = parseQueryParams(req.query);
8+
9+
const packageName = name ? name.toString().toLowerCase().trim() : undefined;
10+
const apiPath = path ? path.toString().trim() : undefined;
11+
12+
res.setHeader('Cache-Control', 'public, s-maxage=600, stale-while-revalidate=300');
13+
14+
if (!packageName || !apiPath) {
15+
res.setHeader('Content-Type', 'application/json');
16+
res.statusCode = 500;
17+
res.json({
18+
error: `Invalid request. You need to specify package name via 'name' query param and desired request 'path'.`,
19+
});
20+
return;
21+
}
22+
23+
const result = await fetch(`https://unpkg.com/${packageName}/${apiPath}`, {
24+
...NEXT_10M_CACHE_HEADER,
25+
redirect: 'manual',
26+
});
27+
28+
if (result.status === 302) {
29+
const location = result.headers.get('location');
30+
31+
if (!location) {
32+
res.setHeader('Content-Type', 'application/json');
33+
res.statusCode = 502;
34+
res.json({
35+
error: 'Invalid response. Unpkg returned a redirect without a location header.',
36+
});
37+
return;
38+
}
39+
40+
const redirectResult = await fetch(new URL(location, 'https://unpkg.com').toString(), {
41+
...NEXT_10M_CACHE_HEADER,
42+
});
43+
44+
if ('status' in redirectResult && redirectResult.status !== 200) {
45+
res.statusCode = redirectResult.status;
46+
res.json({});
47+
return;
48+
}
49+
50+
res.setHeader('Content-Type', 'text/plain');
51+
res.statusCode = 200;
52+
res.write(await redirectResult.text());
53+
res.end();
54+
return;
55+
}
56+
57+
if ('status' in result && result.status !== 200) {
58+
res.statusCode = result.status;
59+
res.json({});
60+
return;
61+
}
62+
63+
res.setHeader('Content-Type', 'application/json');
64+
res.statusCode = 200;
65+
res.json(await result.json());
66+
}

0 commit comments

Comments
 (0)