@lovalingo/lovalingo 0.5.21 → 0.5.23

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 +48 -1
  2. package/package.json +1 -1
package/dist/utils/api.js CHANGED
@@ -1,9 +1,48 @@
1
1
  import { warnDebug, errorDebug } from './logger';
2
+ const OK_HTTP_STATUSES = new Set([200, 201]);
3
+ function getNavigationResponseStatus() {
4
+ if (typeof performance === "undefined" || typeof performance.getEntriesByType !== "function")
5
+ return null;
6
+ const entries = performance.getEntriesByType("navigation");
7
+ if (!entries || entries.length === 0)
8
+ return null;
9
+ const entry = entries[0];
10
+ const rawStatus = entry?.responseStatus;
11
+ const status = typeof rawStatus === "number" ? Math.floor(rawStatus) : NaN;
12
+ if (!Number.isFinite(status) || status <= 0)
13
+ return null;
14
+ return status;
15
+ }
16
+ // Why: avoid tracking misses/pageviews for non-OK responses (404/5xx) so bad routes don't pollute discovery.
17
+ function isOkHttpStatus(status) {
18
+ if (typeof status !== "number")
19
+ return true;
20
+ return OK_HTTP_STATUSES.has(status);
21
+ }
22
+ function normalizeApiBase(raw) {
23
+ const input = (raw || "").toString().trim();
24
+ if (!input)
25
+ return "https://cdn.lovalingo.com";
26
+ let base = input.replace(/\/functions\/v1\/?$/i, "").replace(/\/$/, "");
27
+ try {
28
+ const parsed = new URL(base);
29
+ const host = parsed.hostname.toLowerCase();
30
+ // Why: runtime endpoints moved off Supabase Edge Functions; fall back to CDN if a Supabase host is supplied.
31
+ if (host.endsWith(".supabase.co")) {
32
+ warnDebug("LovalingoAPI", `apiBase points to Supabase (${host}); falling back to https://cdn.lovalingo.com`);
33
+ return "https://cdn.lovalingo.com";
34
+ }
35
+ }
36
+ catch {
37
+ // Ignore parse errors and keep the raw base.
38
+ }
39
+ return base;
40
+ }
2
41
  export class LovalingoAPI {
3
42
  constructor(apiKey, apiBase, pathConfig, editKey) {
4
43
  this.entitlements = null;
5
44
  this.apiKey = apiKey;
6
- this.apiBase = apiBase;
45
+ this.apiBase = normalizeApiBase(apiBase);
7
46
  this.pathConfig = pathConfig;
8
47
  this.editKey = editKey;
9
48
  }
@@ -126,6 +165,9 @@ export class LovalingoAPI {
126
165
  try {
127
166
  if (!this.hasApiKey())
128
167
  return;
168
+ const status = getNavigationResponseStatus();
169
+ if (!isOkHttpStatus(status))
170
+ return;
129
171
  const params = new URLSearchParams();
130
172
  params.set("key", this.apiKey);
131
173
  params.set("path", pathOrUrl);
@@ -154,6 +196,10 @@ export class LovalingoAPI {
154
196
  return null;
155
197
  if (!Array.isArray(misses) || misses.length === 0)
156
198
  return null;
199
+ const status = getNavigationResponseStatus();
200
+ if (!isOkHttpStatus(status)) {
201
+ return { ignored: true, reason: "http_status" };
202
+ }
157
203
  const pathParam = this.buildPathParam(opts?.pathOrUrl);
158
204
  const response = await fetch(`${this.apiBase}/functions/v1/misses`, {
159
205
  method: "POST",
@@ -164,6 +210,7 @@ export class LovalingoAPI {
164
210
  path: pathParam,
165
211
  source_locale: opts?.sourceLocale,
166
212
  locales: Array.isArray(opts?.locales) ? opts?.locales : undefined,
213
+ page_status: typeof status === "number" ? status : undefined,
167
214
  misses,
168
215
  }),
169
216
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lovalingo/lovalingo",
3
- "version": "0.5.21",
3
+ "version": "0.5.23",
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",