Skip to content

Commit

Permalink
finalized changes, used edge SSR route, added html-escaper (h/t to @c…
Browse files Browse the repository at this point in the history
…ramforce for the protip)
  • Loading branch information
steven-tey committed Sep 23, 2022
1 parent 33e9d07 commit 14c6eab
Show file tree
Hide file tree
Showing 14 changed files with 9,335 additions and 2,842 deletions.
51 changes: 49 additions & 2 deletions components/app/add-link-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
LoadingCircle,
AlertCircleFill,
ChevronRight,
UploadCloud,
} from "@/components/shared/icons";
import { useDebounce } from "use-debounce";
import TextareaAutosize from "react-textarea-autosize";
Expand Down Expand Up @@ -311,14 +312,27 @@ function AdvancedSettings({ data, setData, debouncedUrl }) {
fetch(`/api/edge/description?url=${debouncedUrl}`).then(async (res) => {
if (res.status === 200) {
const results = await res.json();
setData((prev) => ({ ...prev, title: results }));
setData((prev) => ({ ...prev, description: results }));
setGeneratingDescription(false);
}
});
},
[debouncedUrl]
);

const onChangePicture = useCallback(
(e) => {
const file = e.target.files[0];
const reader = new FileReader();
reader.onload = (e) => {
setData((prev) => ({ ...prev, image: e.target.result }));
};
reader.readAsDataURL(file);
},
[setData]
);
console.log(image);

return (
<div>
<button
Expand All @@ -335,7 +349,7 @@ function AdvancedSettings({ data, setData, debouncedUrl }) {
</button>

{expanded && (
<div className="mt-4">
<div className="mt-4 grid gap-5">
<div>
<div className="flex justify-between items-center">
<label
Expand Down Expand Up @@ -375,6 +389,39 @@ function AdvancedSettings({ data, setData, debouncedUrl }) {
/>
</div>
</div>

<div>
<p className="block text-sm font-medium text-gray-700">OG Image</p>
<label
htmlFor="image"
className="group flex flex-col justify-center items-center mt-1 h-[10.5rem] cursor-pointer rounded-md border border-gray-300 bg-white hover:bg-gray-50 shadow-sm transition-all"
>
{image ? (
<img
src={image}
alt="Preview"
className="object-cover h-full w-full rounded-md hover:brightness-95 transition-all"
/>
) : (
<>
<UploadCloud className="h-7 w-7 text-gray-500 group-hover:scale-110 group-active:scale-95 transition-all duration-75" />
<p className="text-gray-500 text-sm mt-2">
Recommended: 1200 x 627 pixels
</p>
<span className="sr-only">OG Image upload</span>
</>
)}
</label>
<div className="flex mt-1 rounded-md shadow-sm">
<input
id="image"
name="image"
type="file"
className="sr-only"
onChange={onChangePicture}
/>
</div>
</div>
</div>
)}
</div>
Expand Down
1 change: 1 addition & 0 deletions lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export const RESERVED_KEYS = [
"new",
"info",
"demo",
"proxy",
];

export const COUNTRIES: { [key: string]: string } = {
Expand Down
16 changes: 13 additions & 3 deletions lib/middleware/link.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Redis } from "@upstash/redis";

const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(10, "10 s"),
limiter: Ratelimit.slidingWindow(10, "60 s"),
});

export default async function LinkMiddleware(
Expand All @@ -34,11 +34,21 @@ export default async function LinkMiddleware(
`${hostname}:links`,
key
);
const target = response?.url;
const { url: target, description, image } = response || {};

if (target) {
const isBot =
url.searchParams.get("bot") ||
/bot/i.test(req.headers.get("user-agent"));

ev.waitUntil(recordClick(hostname, req, key)); // increment click count
res = NextResponse.redirect(target);

if (image && description && isBot) {
url.pathname = `/proxy/${hostname}/${key}`;
res = NextResponse.rewrite(url);
} else {
res = NextResponse.redirect(target);
}
} else {
url.pathname = "/";
res = NextResponse.redirect(url);
Expand Down
14 changes: 9 additions & 5 deletions lib/upstash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,15 @@ export async function setKey(
hostname: string,
key: string,
url: string,
title?: string
title?: string,
description?: string, // only for Pro users: customize description
image?: string // only for Pro users: customize OG image
) {
return await redis.hsetnx(`${hostname}:links`, key, {
url,
title: title || (await getTitleFromUrl(url)),
description,
image,
timestamp: Date.now(),
});
}
Expand Down Expand Up @@ -147,16 +151,16 @@ export async function addLink(
url: string,
key?: string, // if key is provided, it will be used
title?: string, // if title is provided, it will be used
description?: string,
image?: string,
description?: string, // only for Pro users: customize description
image?: string, // only for Pro users: customize OG image
userId?: string // only applicable for dub.sh links
) {
if (hostname === "dub.sh" && key && RESERVED_KEYS.includes(key)) {
return null; // reserved keys for dub.sh
}
const response = key
? await setKey(hostname, key, url, title)
: await setRandomKey(hostname, url, title);
? await setKey(hostname, key, url, title, description, image)
: await setRandomKey(hostname, url, title); // not possible to add description and image for random keys (only for dub.sh landing page input)

if (response === 1) {
return await redis.zadd(
Expand Down
22 changes: 22 additions & 0 deletions lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,28 @@ export const getTitleFromUrl = async (url: string) => {
return title;
};

export const getDescriptionFromUrl = async (url: string) => {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 2000); // timeout if it takes longer than 2 seconds
const description = await fetch(url, { signal: controller.signal })
.then((res) => {
clearTimeout(timeoutId);
return res.text();
})
.then((body: string) => {
let match = body.match(/<meta name="description" content="(.*?)"\/>/g); // regular expression to parse contents of the description meta tag
if (!match || typeof match[0] !== "string") return "No description found"; // if no title found, return "No title found"
let description = match[0].match(/content="(.*)"\/>/).pop();
if (!description) return "No description found";
return description;
})
.catch((err) => {
console.log(err);
return "No description found"; // if there's an error, return "No title found"
});
return description;
};

export const timeAgo = (timestamp: number): string => {
if (!timestamp) return "never";
return `${ms(Date.now() - timestamp)} ago`;
Expand Down
5 changes: 4 additions & 1 deletion middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ export const config = {

export default async function middleware(req: NextRequest, ev: NextFetchEvent) {
const { hostname, key } = parse(req);
const home = hostname === "dub.sh" || hostname === "localhost:3000";
const home =
hostname === "dub.sh" ||
hostname === "preview.dub.sh" ||
hostname === "localhost:3000";
const app = hostname === "app.dub.sh" || hostname === "app.localhost:3000";

if (app) {
Expand Down
1 change: 1 addition & 0 deletions next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ module.exports = {
"avatar.tobi.sh",
"faisalman.github.io",
"avatars.dicebear.com",
"res.cloudinary.com",
],
},
};
Loading

0 comments on commit 14c6eab

Please sign in to comment.