Skip to content

Commit 730df15

Browse files
committed
refactor
1 parent a8c8cb5 commit 730df15

File tree

6 files changed

+394
-67
lines changed

6 files changed

+394
-67
lines changed

apps/studio-next/package.json

+12-10
Original file line numberDiff line numberDiff line change
@@ -16,31 +16,33 @@
1616
"@asyncapi/protobuf-schema-parser": "^3.2.8",
1717
"@asyncapi/react-component": "^1.2.2",
1818
"@asyncapi/specs": "^6.5.4",
19+
"@codemirror/view": "^6.26.3",
1920
"@ebay/nice-modal-react": "^1.2.10",
2021
"@headlessui/react": "^1.7.4",
2122
"@hookstate/core": "^4.0.0-rc21",
2223
"@monaco-editor/react": "^4.4.6",
23-
"@tippyjs/react": "^4.2.6",
24-
"js-base64": "^3.7.3",
25-
"js-file-download": "^0.4.12",
26-
"js-yaml": "^4.1.0",
27-
"monaco-editor": "0.34.1",
28-
"monaco-yaml": "4.0.2",
29-
"react-hot-toast": "2.4.0",
30-
"react-icons": "^4.6.0",
31-
"reactflow": "^11.2.0",
3224
"@stoplight/yaml": "^4.3.0",
33-
"@codemirror/view": "^6.26.3",
25+
"@tippyjs/react": "^4.2.6",
3426
"@types/node": "20.4.6",
3527
"@types/react": "18.2.18",
3628
"@types/react-dom": "18.2.7",
3729
"autoprefixer": "10.4.14",
30+
"axios": "^1.7.3",
3831
"codemirror": "^6.0.1",
32+
"crawler-user-agents": "^1.0.142",
3933
"eslint-config-next": "13.4.12",
34+
"js-base64": "^3.7.3",
35+
"js-file-download": "^0.4.12",
36+
"js-yaml": "^4.1.0",
37+
"monaco-editor": "0.34.1",
38+
"monaco-yaml": "4.0.2",
4039
"next": "14.2.3",
4140
"postcss": "8.4.31",
4241
"react": "18.2.0",
4342
"react-dom": "18.2.0",
43+
"react-hot-toast": "2.4.0",
44+
"react-icons": "^4.6.0",
45+
"reactflow": "^11.2.0",
4446
"tailwindcss": "3.3.3",
4547
"tippy.js": "^6.3.7",
4648
"typescript": "5.1.6",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import { NextRequest, NextResponse } from "next/server";
2+
import parseURL from "@/helpers/parser";
3+
import { DocumentInfo } from "@/types";
4+
import axios from "axios";
5+
import { metadata } from "@/app/page";
6+
7+
export async function GET(request: NextRequest) {
8+
const Base64searchParams = request.nextUrl.searchParams.get('base64');
9+
const URLsearchParams = request.nextUrl.searchParams.get('url');
10+
11+
try {
12+
if (!Base64searchParams && !URLsearchParams) return new NextResponse(null, { status: 200 });
13+
let info: DocumentInfo | null = null;
14+
15+
if (Base64searchParams) {
16+
// directly run the parsing function
17+
info = await parseURL(Base64searchParams);
18+
}
19+
if (URLsearchParams) {
20+
// fetch the document information from the URL
21+
try {
22+
const response = await axios.get(URLsearchParams);
23+
if (response.status === 200) {
24+
info = await parseURL(response.data);
25+
} else {
26+
return new NextResponse("Not a valid URL", { status: 500 });
27+
}
28+
} catch (error) {
29+
return new NextResponse("Not a valid URL", { status: 500 });
30+
}
31+
}
32+
33+
if (!info) {
34+
const ogImage = "https://raw.githubusercontent.com/asyncapi/studio/master/apps/studio-next/public/img/meta-studio-og-image.jpeg";
35+
36+
const crawlerInfo = `
37+
<!DOCTYPE html>
38+
<html lang="en">
39+
<head>
40+
<meta charset="UTF-8">
41+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
42+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
43+
<title>"${metadata.openGraph?.title}"</title>
44+
<meta property="og:title" content="${metadata.openGraph?.title}" />
45+
<meta property="og:description" content="${metadata.openGraph?.description}" />
46+
<meta property="og:url" content="${metadata.openGraph?.url}" />
47+
<meta property="og:image" content="${ogImage}" />
48+
`
49+
return new NextResponse(crawlerInfo, {
50+
headers: {
51+
'Content-Type': 'text/html',
52+
},
53+
})
54+
}
55+
56+
let ogImageParams = new URLSearchParams();
57+
58+
if (info.title) {
59+
ogImageParams.append('title', info.title.toString());
60+
}
61+
if (info.description) {
62+
ogImageParams.append('description', info.description.toString());
63+
}
64+
if (info.numServers) {
65+
ogImageParams.append('numServers', info.numServers.toString());
66+
}
67+
if (info.numChannels) {
68+
ogImageParams.append('numChannels', info.numChannels.toString());
69+
}
70+
if (info.numOperations) {
71+
ogImageParams.append('numOperations', info.numOperations.toString());
72+
}
73+
if (info.numMessages) {
74+
ogImageParams.append('numMessages', info.numMessages.toString());
75+
}
76+
77+
const ogImageurl = `https://ogp-studio.netlify.app/api/og?${ogImageParams.toString()}`;
78+
79+
const crawlerInfo = `
80+
<!DOCTYPE html>
81+
<html lang="en">
82+
<head>
83+
<meta charset="UTF-8">
84+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
85+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
86+
<title>${info.title}</title>
87+
${info.title ? `<meta property="og:title" content="${info.title}" />` : ''}
88+
${info.description ? `<meta property="og:description" content="${info.description}" />` : ''}
89+
<meta property="og:image" content=${ogImageurl} />
90+
</head>
91+
</html>
92+
`;
93+
94+
return new NextResponse(crawlerInfo, {
95+
status: 200,
96+
headers: {
97+
'Content-Type': 'text/html',
98+
},
99+
});
100+
} catch (err) {
101+
return new NextResponse("Not a valid URL", { status: 500 });
102+
}
103+
}
104+
+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { Input, Parser } from '@asyncapi/parser';
2+
import { DocumentInfo } from '@/types';
3+
4+
export default async function parseURL(asyncapiDocument: string): Promise<DocumentInfo | null> {
5+
const parser = new Parser();
6+
7+
const base64Regex = /^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?$/;
8+
9+
let decodedDocument: Input = "";
10+
if (base64Regex.test(asyncapiDocument)) {
11+
decodedDocument = Buffer.from(asyncapiDocument, "base64").toString("utf-8");
12+
} else {
13+
decodedDocument = asyncapiDocument;
14+
}
15+
16+
const { document, diagnostics } = await parser.parse(decodedDocument);
17+
18+
if (diagnostics.length) {
19+
return null;
20+
}
21+
22+
let title = document?.info().title();
23+
if (title) {
24+
title = title.length <= 20 ? title : title.slice(0, 20) + "...";
25+
}
26+
const version = document?.info().version();
27+
28+
let description = document?.info().description();
29+
if (description) {
30+
description = description.length <= 100 ? description : description.slice(0, 100) + "...";
31+
}
32+
33+
const servers = document?.allServers();
34+
const channels = document?.allChannels();
35+
const operations = document?.allOperations();
36+
const messages = document?.allMessages();
37+
38+
const numServers = servers?.length;
39+
const numChannels = channels?.length;
40+
const numOperations = operations?.length;
41+
const numMessages = messages?.length;
42+
43+
const response = {
44+
title,
45+
version,
46+
description,
47+
numServers,
48+
numChannels,
49+
numOperations,
50+
numMessages
51+
};
52+
return response;
53+
}

apps/studio-next/src/middleware.ts

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { NextRequest, NextResponse, userAgent } from "next/server";
2+
import crawlers from 'crawler-user-agents';
3+
4+
export async function middleware(request: NextRequest) {
5+
const userAgents = crawlers.map(crawler => crawler.pattern);
6+
const requestInfo = userAgent(request);
7+
const res = NextResponse.next();
8+
9+
for (const ua of userAgents) {
10+
if (requestInfo.ua.toLowerCase().includes(ua.toLowerCase())) {
11+
12+
const documentURL = request.nextUrl.searchParams.get("url");
13+
const encodedDocument = request.nextUrl.searchParams.get("base64");
14+
15+
if (!encodedDocument && !documentURL) {
16+
return res;
17+
}
18+
if (encodedDocument) {
19+
return NextResponse.rewrite(new URL(`/api/crawler?base64=${encodedDocument}`, request.url));
20+
}
21+
if (documentURL) {
22+
return NextResponse.rewrite(new URL(`/api/crawler?url=${documentURL}`, request.url));
23+
}
24+
}
25+
}
26+
return res;
27+
}

apps/studio-next/src/types.ts

+10
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
11
import type specs from '@asyncapi/specs';
22

33
export type SpecVersions = keyof typeof specs.schemas;
4+
5+
export interface DocumentInfo {
6+
title? : string,
7+
version? : string,
8+
description? : string,
9+
numServers? : number,
10+
numChannels? : number,
11+
numOperations? : number,
12+
numMessages?: number
13+
}

0 commit comments

Comments
 (0)