@lovalingo/lovalingo 0.5.22 → 0.5.24

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.
Files changed (2) hide show
  1. package/dist/utils/api.js +40 -1
  2. package/package.json +1 -1
package/dist/utils/api.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { warnDebug, errorDebug } from './logger';
2
2
  const OK_HTTP_STATUSES = new Set([200, 201]);
3
+ const NOT_FOUND_TITLE_HINTS = /404|not found|page not found|page missing|does not exist|error 404/i;
3
4
  function getNavigationResponseStatus() {
4
5
  if (typeof performance === "undefined" || typeof performance.getEntriesByType !== "function")
5
6
  return null;
@@ -19,11 +20,43 @@ function isOkHttpStatus(status) {
19
20
  return true;
20
21
  return OK_HTTP_STATUSES.has(status);
21
22
  }
23
+ function looksLikeNotFoundDocument() {
24
+ if (typeof document === "undefined")
25
+ return false;
26
+ const title = (document.title || "").toString().trim().toLowerCase();
27
+ if (title && NOT_FOUND_TITLE_HINTS.test(title))
28
+ return true;
29
+ const bodyText = (document.body?.textContent || "").toString().slice(0, 2000).toLowerCase();
30
+ if (!bodyText)
31
+ return false;
32
+ const has404 = /\b404\b/.test(bodyText);
33
+ const hasNotFound = /not found|page not found|page missing|does not exist|error 404/.test(bodyText);
34
+ return has404 && hasNotFound;
35
+ }
36
+ function normalizeApiBase(raw) {
37
+ const input = (raw || "").toString().trim();
38
+ if (!input)
39
+ return "https://cdn.lovalingo.com";
40
+ let base = input.replace(/\/functions\/v1\/?$/i, "").replace(/\/$/, "");
41
+ try {
42
+ const parsed = new URL(base);
43
+ const host = parsed.hostname.toLowerCase();
44
+ // Why: runtime endpoints moved off Supabase Edge Functions; fall back to CDN if a Supabase host is supplied.
45
+ if (host.endsWith(".supabase.co")) {
46
+ warnDebug("LovalingoAPI", `apiBase points to Supabase (${host}); falling back to https://cdn.lovalingo.com`);
47
+ return "https://cdn.lovalingo.com";
48
+ }
49
+ }
50
+ catch {
51
+ // Ignore parse errors and keep the raw base.
52
+ }
53
+ return base;
54
+ }
22
55
  export class LovalingoAPI {
23
56
  constructor(apiKey, apiBase, pathConfig, editKey) {
24
57
  this.entitlements = null;
25
58
  this.apiKey = apiKey;
26
- this.apiBase = apiBase;
59
+ this.apiBase = normalizeApiBase(apiBase);
27
60
  this.pathConfig = pathConfig;
28
61
  this.editKey = editKey;
29
62
  }
@@ -149,6 +182,9 @@ export class LovalingoAPI {
149
182
  const status = getNavigationResponseStatus();
150
183
  if (!isOkHttpStatus(status))
151
184
  return;
185
+ // Why: avoid tracking SPA soft-404s (HTTP 200 + "not found" content) so ghost pages don't reappear.
186
+ if (looksLikeNotFoundDocument())
187
+ return;
152
188
  const params = new URLSearchParams();
153
189
  params.set("key", this.apiKey);
154
190
  params.set("path", pathOrUrl);
@@ -181,6 +217,9 @@ export class LovalingoAPI {
181
217
  if (!isOkHttpStatus(status)) {
182
218
  return { ignored: true, reason: "http_status" };
183
219
  }
220
+ if (looksLikeNotFoundDocument()) {
221
+ return { ignored: true, reason: "soft_404" };
222
+ }
184
223
  const pathParam = this.buildPathParam(opts?.pathOrUrl);
185
224
  const response = await fetch(`${this.apiBase}/functions/v1/misses`, {
186
225
  method: "POST",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lovalingo/lovalingo",
3
- "version": "0.5.22",
3
+ "version": "0.5.24",
4
4
  "description": "React translation runtime with i18n routing, deterministic bundles + DOM rules, and zero-flash rendering.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",