@m13v/seo-components 0.30.2 → 0.30.3
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/dm-short-link-redirect.ts +104 -0
- package/src/server.ts +3 -0
package/package.json
CHANGED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { NextRequest } from "next/server";
|
|
2
|
+
|
|
3
|
+
export interface DmShortLinkRedirectConfig {
|
|
4
|
+
/** Site slug, used as a property on the PostHog event (e.g. "pieline"). */
|
|
5
|
+
site: string;
|
|
6
|
+
/**
|
|
7
|
+
* Base URL of the social-autoposter dashboard resolver.
|
|
8
|
+
* Override via env (`SHORT_LINK_RESOLVER_URL`) or pass explicitly. Defaults
|
|
9
|
+
* to https://app.s4l.ai which is where the public /api/short-links/<code>
|
|
10
|
+
* endpoint lives.
|
|
11
|
+
*/
|
|
12
|
+
resolverBase?: string;
|
|
13
|
+
/** Env var holding the PostHog public key (default: `NEXT_PUBLIC_POSTHOG_KEY`). */
|
|
14
|
+
posthogKeyEnv?: string;
|
|
15
|
+
/** Env var holding the PostHog host (default: `NEXT_PUBLIC_POSTHOG_HOST`). */
|
|
16
|
+
posthogHostEnv?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Factory for `GET /r/[code]`.
|
|
21
|
+
*
|
|
22
|
+
* Each per-DM short link maps to a Cal.com / Calendly URL with full UTM and
|
|
23
|
+
* `metadata[utm_*]` so cal_bookings closes the loop on which DM produced the
|
|
24
|
+
* booking. The cached `target_url` is frozen at mint time on the dms row, so
|
|
25
|
+
* the resolver is a single DB read with no config.json dependency.
|
|
26
|
+
*
|
|
27
|
+
* Behavior:
|
|
28
|
+
* 1. Read `code` from the route param. Reject non-alphanumeric / wrong-length.
|
|
29
|
+
* 2. Hit `<resolverBase>/api/short-links/<code>`. The resolver increments
|
|
30
|
+
* dms.short_link_clicks and stamps first/last click timestamps.
|
|
31
|
+
* 3. Fire a PostHog `dm_short_link_clicked` event with dm_id, project,
|
|
32
|
+
* platform, code, site.
|
|
33
|
+
* 4. 302 to the resolved target_url. On miss/error, 302 to "/".
|
|
34
|
+
*/
|
|
35
|
+
export function createDmShortLinkRedirectHandler(config: DmShortLinkRedirectConfig) {
|
|
36
|
+
const {
|
|
37
|
+
site,
|
|
38
|
+
resolverBase = process.env.SHORT_LINK_RESOLVER_URL || "https://app.s4l.ai",
|
|
39
|
+
posthogKeyEnv = "NEXT_PUBLIC_POSTHOG_KEY",
|
|
40
|
+
posthogHostEnv = "NEXT_PUBLIC_POSTHOG_HOST",
|
|
41
|
+
} = config;
|
|
42
|
+
const RESOLVER = resolverBase.replace(/\/+$/, "");
|
|
43
|
+
|
|
44
|
+
return async function GET(
|
|
45
|
+
req: NextRequest,
|
|
46
|
+
ctx: { params: Promise<{ code: string }> }
|
|
47
|
+
) {
|
|
48
|
+
const { code: rawCode } = await ctx.params;
|
|
49
|
+
const code = (rawCode || "").trim();
|
|
50
|
+
const homeUrl = new URL("/", req.url).toString();
|
|
51
|
+
|
|
52
|
+
if (!/^[a-z0-9]{4,32}$/i.test(code)) {
|
|
53
|
+
return Response.redirect(homeUrl, 302);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
let target: string | null = null;
|
|
57
|
+
let dmId: number | null = null;
|
|
58
|
+
let project: string | null = null;
|
|
59
|
+
let platform: string | null = null;
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
const resp = await fetch(`${RESOLVER}/api/short-links/${encodeURIComponent(code)}`, {
|
|
63
|
+
cache: "no-store",
|
|
64
|
+
signal: AbortSignal.timeout(4000),
|
|
65
|
+
});
|
|
66
|
+
if (resp.ok) {
|
|
67
|
+
const body = (await resp.json()) as {
|
|
68
|
+
target_url?: string;
|
|
69
|
+
dm_id?: number;
|
|
70
|
+
project?: string;
|
|
71
|
+
platform?: string;
|
|
72
|
+
};
|
|
73
|
+
if (body.target_url) {
|
|
74
|
+
target = body.target_url;
|
|
75
|
+
dmId = body.dm_id ?? null;
|
|
76
|
+
project = body.project ?? null;
|
|
77
|
+
platform = body.platform ?? null;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
} catch (err) {
|
|
81
|
+
console.error("[dm-short-link/redirect] resolver fetch failed:", err);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const posthogKey = process.env[posthogKeyEnv];
|
|
85
|
+
const posthogHost = (process.env[posthogHostEnv] || "https://us.i.posthog.com").replace(/\/+$/, "");
|
|
86
|
+
if (target && posthogKey && dmId != null) {
|
|
87
|
+
fetch(`${posthogHost}/i/v0/e/`, {
|
|
88
|
+
method: "POST",
|
|
89
|
+
headers: { "Content-Type": "application/json" },
|
|
90
|
+
body: JSON.stringify({
|
|
91
|
+
api_key: posthogKey,
|
|
92
|
+
event: "dm_short_link_clicked",
|
|
93
|
+
distinct_id: `dm_${dmId}`,
|
|
94
|
+
timestamp: new Date().toISOString(),
|
|
95
|
+
properties: { dm_id: dmId, project, platform, code, site },
|
|
96
|
+
}),
|
|
97
|
+
}).catch((err) =>
|
|
98
|
+
console.error("[dm-short-link/redirect] posthog fetch failed:", err)
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return Response.redirect(target || homeUrl, 302);
|
|
103
|
+
};
|
|
104
|
+
}
|
package/src/server.ts
CHANGED
|
@@ -43,6 +43,9 @@ export type { BookCallConfig } from "./lib/book-call-route";
|
|
|
43
43
|
export { createBookCallRedirectHandler } from "./lib/book-call-redirect";
|
|
44
44
|
export type { BookCallRedirectConfig } from "./lib/book-call-redirect";
|
|
45
45
|
|
|
46
|
+
export { createDmShortLinkRedirectHandler } from "./lib/dm-short-link-redirect";
|
|
47
|
+
export type { DmShortLinkRedirectConfig } from "./lib/dm-short-link-redirect";
|
|
48
|
+
|
|
46
49
|
// SeoComponentsStyles was removed in v0.23.0. It injected a prebuilt Tailwind
|
|
47
50
|
// bundle wrapped in `@layer seo-components`, which collided with the
|
|
48
51
|
// consumer's own `@layer utilities` and forced GuideChatPanel / SitemapSidebar
|