@m13v/seo-components 0.41.0 → 0.41.2

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@m13v/seo-components",
3
- "version": "0.41.0",
3
+ "version": "0.41.2",
4
4
  "scripts": {
5
5
  "build:css": "tailwind -i src/_build.css -o dist/styles.css --minify",
6
6
  "lint:mobile-spans": "node scripts/lint-mobile-spans.mjs",
@@ -103,7 +103,24 @@ export function createDmShortLinkRedirectHandler(config: DmShortLinkRedirectConf
103
103
  ) {
104
104
  const { code: rawCode } = await ctx.params;
105
105
  const code = (rawCode || "").trim();
106
- const homeUrl = new URL("/", req.url).toString();
106
+ // Build the public home URL using forwarded headers. On Cloud Run the
107
+ // container is served at `0.0.0.0:8080` internally and Next.js sets
108
+ // `req.url` from that host, so `new URL("/", req.url)` would 302 users to
109
+ // `https://0.0.0.0:8080/` (unreachable). Vercel doesn't have this problem
110
+ // because its proxy rewrites `req.url` to the public host, but we share
111
+ // this handler across Vercel + Cloud Run consumers, so always prefer the
112
+ // forwarded host headers when present.
113
+ const fwdHost =
114
+ req.headers.get("x-forwarded-host") ||
115
+ req.headers.get("host") ||
116
+ "";
117
+ const fwdProto =
118
+ req.headers.get("x-forwarded-proto") ||
119
+ (fwdHost && !fwdHost.includes("localhost") ? "https" : "http");
120
+ const homeUrl =
121
+ fwdHost && !/(?:^|\.)0\.0\.0\.0(?::\d+)?$/.test(fwdHost)
122
+ ? `${fwdProto}://${fwdHost}/`
123
+ : new URL("/", req.url).toString();
107
124
 
108
125
  if (!/^[a-z0-9]{4,32}$/i.test(code)) {
109
126
  return Response.redirect(homeUrl, 302);
@@ -126,6 +143,13 @@ export function createDmShortLinkRedirectHandler(config: DmShortLinkRedirectConf
126
143
  let replyId: number | null = null;
127
144
  let project: string | null = null;
128
145
  let platform: string | null = null;
146
+ // Newsletter rail attribution: the resolver returns broadcast_id +
147
+ // broadcast_product + recipient_email_hash when the code resolves to
148
+ // newsletter_links. We fire `newsletter_short_link_clicked` so PostHog
149
+ // can stitch the click back to the originating broadcast + recipient.
150
+ let broadcastId: number | null = null;
151
+ let broadcastProduct: string | null = null;
152
+ let recipientEmailHash: string | null = null;
129
153
 
130
154
  try {
131
155
  const params = new URLSearchParams();
@@ -152,6 +176,9 @@ export function createDmShortLinkRedirectHandler(config: DmShortLinkRedirectConf
152
176
  reply_id?: number;
153
177
  project?: string;
154
178
  platform?: string;
179
+ broadcast_id?: number;
180
+ broadcast_product?: string;
181
+ recipient_email_hash?: string;
155
182
  };
156
183
  if (body.target_url) {
157
184
  target = body.target_url;
@@ -160,6 +187,9 @@ export function createDmShortLinkRedirectHandler(config: DmShortLinkRedirectConf
160
187
  replyId = body.reply_id ?? null;
161
188
  project = body.project ?? null;
162
189
  platform = body.platform ?? null;
190
+ broadcastId = body.broadcast_id ?? null;
191
+ broadcastProduct = body.broadcast_product ?? null;
192
+ recipientEmailHash = body.recipient_email_hash ?? null;
163
193
  }
164
194
  }
165
195
  } catch (err) {
@@ -231,6 +261,34 @@ export function createDmShortLinkRedirectHandler(config: DmShortLinkRedirectConf
231
261
  );
232
262
  }
233
263
 
264
+ // Newsletter rail event. distinct_id is the recipient_email_hash so
265
+ // every click from the same recipient stitches under one identity in
266
+ // PostHog. Properties carry broadcast_id + product so HogQL queries
267
+ // can attribute clicks (and downstream signup events on the same
268
+ // session) back to a specific broadcast x recipient pair.
269
+ if (!isBot && target && posthogKey && broadcastId != null) {
270
+ fetch(`${posthogHost}/i/v0/e/`, {
271
+ method: "POST",
272
+ headers: { "Content-Type": "application/json" },
273
+ body: JSON.stringify({
274
+ api_key: posthogKey,
275
+ event: "newsletter_short_link_clicked",
276
+ distinct_id: recipientEmailHash || `newsletter_${broadcastId}`,
277
+ timestamp: new Date().toISOString(),
278
+ properties: {
279
+ broadcast_id: broadcastId,
280
+ broadcast_product: broadcastProduct,
281
+ recipient_email_hash: recipientEmailHash,
282
+ project,
283
+ code,
284
+ site,
285
+ },
286
+ }),
287
+ }).catch((err) =>
288
+ console.error("[dm-short-link/redirect] posthog fetch failed:", err)
289
+ );
290
+ }
291
+
234
292
  return Response.redirect(target || homeUrl, 302);
235
293
  };
236
294
  }
@@ -15,7 +15,7 @@ import { discoverGuides } from "./discover-guides";
15
15
  import { logAiUsage } from "./ai-usage";
16
16
 
17
17
  const MODEL_ID = "gemini-flash-latest";
18
- const MAX_TOOL_ROUNDS = 3;
18
+ const MAX_TOOL_ROUNDS = 50;
19
19
  const RATE_LIMIT_MAX = 20;
20
20
  const RATE_LIMIT_WINDOW_MS = 60 * 60 * 1000;
21
21