@smalk/nextjs-ads 0.1.0 → 1.2.0
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/CHANGELOG.md +30 -0
- package/README.md +78 -1
- package/dist/app.cjs +69 -23
- package/dist/app.cjs.map +1 -1
- package/dist/app.js +14 -3
- package/dist/app.js.map +1 -1
- package/dist/chunk-4QWOGJYF.js +219 -0
- package/dist/chunk-4QWOGJYF.js.map +1 -0
- package/dist/{chunk-YA6M2IA4.js → chunk-INI3DN37.js} +5 -5
- package/dist/chunk-INI3DN37.js.map +1 -0
- package/dist/index.cjs +232 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +89 -5
- package/dist/index.d.ts +89 -5
- package/dist/index.js +25 -3
- package/dist/middleware.cjs +60 -25
- package/dist/middleware.cjs.map +1 -1
- package/dist/middleware.js +2 -2
- package/dist/pages.cjs +63 -23
- package/dist/pages.cjs.map +1 -1
- package/dist/pages.d.cts +1 -0
- package/dist/pages.d.ts +1 -0
- package/dist/pages.js +8 -3
- package/dist/pages.js.map +1 -1
- package/package.json +10 -6
- package/dist/chunk-HX4ZGDYQ.js +0 -31
- package/dist/chunk-HX4ZGDYQ.js.map +0 -1
- package/dist/chunk-YA6M2IA4.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/config.ts","../src/freshness/route.ts","../src/freshness/activeUrlsStore.ts","../src/freshness/activeUrlsCache.ts","../src/freshness/middleware.ts","../src/index.ts"],"sourcesContent":["export interface SmalkConfig {\n /** Full API base including version path, e.g. https://api.smalk.ai/api/v1 */\n apiBase: string;\n apiKey: string;\n projectKey: string;\n cronSecret: string;\n pluginVersion: string;\n publicHost?: string;\n}\n\n/**\n * Backwards-compatible config shape for legacy callers (pre-P2). Adds an\n * `apiBaseUrl` alias for `apiBase`. New code should use `getSmalkConfig()`\n * and read `apiBase` directly.\n */\nexport interface LegacySmalkConfig extends SmalkConfig {\n /**\n * @deprecated Use `apiBase` instead.\n *\n * Pre-P2.5 shape: host-only (e.g. `https://api.smalk.ai`), and external\n * publisher code appended `/api/v1/...`. To preserve that shape after the\n * P2.5 Next config unify (which moved `/api/v1` into the canonical\n * `apiBase`), this field strips a trailing `/api/vN` segment from\n * `apiBase` before exposing it. New code should read `apiBase` directly.\n */\n apiBaseUrl: string;\n}\n\nconst DEFAULT_API_BASE = \"https://api.smalk.ai/api/v1\";\nconst PLUGIN_VERSION = \"1.1.0\";\n\nexport function getSmalkConfig(): SmalkConfig {\n const apiBase = process.env.SMALK_API_BASE || process.env.SMALK_API_BASE_URL || DEFAULT_API_BASE;\n const apiKey = process.env.SMALK_API_KEY ?? \"\";\n const projectKey = process.env.SMALK_PROJECT_KEY ?? \"\";\n const cronSecret = process.env.SMALK_CRON_SECRET ?? \"\";\n const publicHost = process.env.SMALK_PUBLIC_HOST || undefined;\n\n return {\n apiBase,\n apiKey,\n projectKey,\n cronSecret,\n pluginVersion: PLUGIN_VERSION,\n publicHost,\n };\n}\n\n/**\n * Strip a trailing `/api/vN` (or `/api/vN/`) segment from an API base URL.\n * Preserves backward compatibility with publishers building URLs like\n * `${apiBaseUrl}/api/v1/...` — without stripping, those would double-prefix\n * to `/api/v1/api/v1/...` and 404.\n */\nfunction stripApiPathSuffix(apiBase: string): string {\n return apiBase.replace(/\\/api\\/v\\d+\\/?$/, \"\");\n}\n\n/**\n * Legacy loader retained for external consumers (publisher apps) that import\n * `loadConfig` from `@smalk/nextjs-ads`. Returns the canonical `SmalkConfig`\n * plus an `apiBaseUrl` alias whose shape matches the pre-P2.5 contract\n * (host-only, no `/api/v1` suffix). New code should prefer `getSmalkConfig()`\n * and read `apiBase` directly.\n */\nexport function loadConfig(): LegacySmalkConfig {\n const cfg = getSmalkConfig();\n return { ...cfg, apiBaseUrl: stripApiPathSuffix(cfg.apiBase) };\n}\n","import { revalidatePath } from \"next/cache\";\nimport { NextRequest, NextResponse } from \"next/server\";\n\nimport { getSmalkConfig } from \"../config\";\nimport { cacheActiveUrls } from \"./activeUrlsCache\";\n\nexport async function smalkFreshnessRevalidateHandler(req: NextRequest) {\n const cfg = getSmalkConfig();\n const secret = req.headers.get(\"x-smalk-cron-secret\");\n if (!secret || secret !== cfg.cronSecret) {\n return NextResponse.json({ error: \"unauthorized\" }, { status: 401 });\n }\n\n const endpoint = `${cfg.apiBase}/projects/${cfg.projectKey}/ads/inventory/active-ad-urls/`;\n const resp = await fetch(endpoint, {\n headers: {\n \"X-API-Key\": cfg.apiKey,\n \"X-Smalk-CMS\": \"nextjs\",\n \"X-Smalk-Plugin\": cfg.pluginVersion,\n },\n cache: \"no-store\",\n });\n if (!resp.ok) {\n return NextResponse.json({ error: `api ${resp.status}` }, { status: 502 });\n }\n const payload = (await resp.json()) as { urls: { url: string }[] };\n const paths = payload.urls\n .map((u) => safePath(u.url, cfg.publicHost))\n .filter((p): p is string => Boolean(p));\n\n await cacheActiveUrls(paths);\n\n let revalidated = 0;\n for (const p of paths) {\n try {\n revalidatePath(p);\n revalidated++;\n } catch (e) {\n console.warn(`[smalk] revalidatePath failed for ${p}`, e);\n }\n }\n\n return NextResponse.json({\n ok: true,\n total: paths.length,\n revalidated,\n ran_at: new Date().toISOString(),\n });\n}\n\nfunction safePath(url: string, publicHost?: string): string | null {\n try {\n const u = new URL(url);\n if (publicHost && u.host !== publicHost) return null;\n return u.pathname;\n } catch {\n return null;\n }\n}\n","export interface ActiveUrlsCachedPayload {\n paths: string[];\n updated_at: number;\n}\n\nexport interface ActiveUrlsStore {\n read(): Promise<ActiveUrlsCachedPayload | null>;\n write(payload: ActiveUrlsCachedPayload): Promise<void>;\n}\n\n// In-memory store (per-process, fast, lost on restart — good for tests).\nexport class InMemoryActiveUrlsStore implements ActiveUrlsStore {\n private value: ActiveUrlsCachedPayload | null = null;\n async read() { return this.value; }\n async write(payload: ActiveUrlsCachedPayload) { this.value = payload; }\n}\n\n// Filesystem fallback — uses os.tmpdir() (current behavior, kept for Node dev).\nexport class FilesystemActiveUrlsStore implements ActiveUrlsStore {\n constructor(private filePath?: string) {}\n async read(): Promise<ActiveUrlsCachedPayload | null> {\n const fs = await import(\"node:fs/promises\");\n const path = await import(\"node:path\");\n const os = await import(\"node:os\");\n const f = this.filePath || path.join(os.tmpdir(), \"smalk-active-ad-paths.json\");\n try {\n return JSON.parse(await fs.readFile(f, \"utf8\"));\n } catch {\n return null;\n }\n }\n async write(payload: ActiveUrlsCachedPayload) {\n const fs = await import(\"node:fs/promises\");\n const path = await import(\"node:path\");\n const os = await import(\"node:os\");\n const f = this.filePath || path.join(os.tmpdir(), \"smalk-active-ad-paths.json\");\n await fs.mkdir(path.dirname(f), { recursive: true });\n await fs.writeFile(f, JSON.stringify(payload));\n }\n}\n\n// Vercel KV adapter — requires @vercel/kv installed by the publisher.\n// We don't bundle the dep; lazy-import via a non-literal specifier so\n// bundlers (vite/vitest/tsup/webpack) don't try to resolve it at build time.\nconst VERCEL_KV_MODULE = \"@vercel/kv\";\nasync function loadVercelKv(): Promise<{ kv: { get(k: string): Promise<unknown>; set(k: string, v: string): Promise<unknown> } }> {\n // Indirect specifier — opaque to bundler static analysis.\n return (await import(/* @vite-ignore */ /* webpackIgnore: true */ VERCEL_KV_MODULE)) as {\n kv: { get(k: string): Promise<unknown>; set(k: string, v: string): Promise<unknown> };\n };\n}\n\nexport class VercelKVActiveUrlsStore implements ActiveUrlsStore {\n private readonly key: string;\n constructor(key = \"smalk:active-ad-paths\") { this.key = key; }\n async read(): Promise<ActiveUrlsCachedPayload | null> {\n const { kv } = await loadVercelKv();\n const raw = await kv.get(this.key);\n if (!raw) return null;\n try { return typeof raw === \"string\" ? JSON.parse(raw) : (raw as ActiveUrlsCachedPayload); }\n catch { return null; }\n }\n async write(payload: ActiveUrlsCachedPayload) {\n const { kv } = await loadVercelKv();\n await kv.set(this.key, JSON.stringify(payload));\n }\n}\n\n// Cloudflare KV adapter — publisher passes the KVNamespace binding.\nexport class CloudflareKVActiveUrlsStore implements ActiveUrlsStore {\n constructor(private namespace: { get(key: string): Promise<string | null>; put(key: string, value: string): Promise<void> }, private key = \"smalk:active-ad-paths\") {}\n async read(): Promise<ActiveUrlsCachedPayload | null> {\n const raw = await this.namespace.get(this.key);\n return raw ? JSON.parse(raw) : null;\n }\n async write(payload: ActiveUrlsCachedPayload) {\n await this.namespace.put(this.key, JSON.stringify(payload));\n }\n}\n\n// Module-level default — process-wide singleton, swap with setActiveUrlsStore().\nlet currentStore: ActiveUrlsStore = new FilesystemActiveUrlsStore();\n\nexport function getActiveUrlsStore(): ActiveUrlsStore {\n return currentStore;\n}\n\nexport function setActiveUrlsStore(store: ActiveUrlsStore): void {\n currentStore = store;\n}\n","import { getActiveUrlsStore, type ActiveUrlsCachedPayload } from \"./activeUrlsStore\";\n\nexport async function cacheActiveUrls(paths: string[]) {\n await getActiveUrlsStore().write({ paths, updated_at: Date.now() });\n}\n\nexport async function readActiveUrls(): Promise<ActiveUrlsCachedPayload | null> {\n return getActiveUrlsStore().read();\n}\n\n// Re-export for publishers configuring their own store\nexport { getActiveUrlsStore, setActiveUrlsStore } from \"./activeUrlsStore\";\nexport type { ActiveUrlsStore, ActiveUrlsCachedPayload } from \"./activeUrlsStore\";\n","import { NextRequest, NextResponse } from \"next/server\";\n\nimport { readActiveUrls } from \"./activeUrlsCache\";\n\nlet pathsCache: Set<string> | null = null;\nlet pathsLoadedAt = 0;\nconst RELOAD_INTERVAL_MS = 60_000;\n\nasync function getPaths(): Promise<Set<string>> {\n const now = Date.now();\n if (!pathsCache || now - pathsLoadedAt > RELOAD_INTERVAL_MS) {\n const data = await readActiveUrls();\n pathsCache = new Set(data?.paths ?? []);\n pathsLoadedAt = now;\n }\n return pathsCache;\n}\n\nexport async function smalkFreshnessMiddleware(req: NextRequest): Promise<NextResponse | undefined> {\n const paths = await getPaths();\n if (!paths.has(req.nextUrl.pathname)) return undefined;\n const res = NextResponse.next();\n // Day-bucket stable Last-Modified so real users with If-Modified-Since\n // still get 304s within the day; AI crawlers (which hit infrequently)\n // get a fresh signal on the next UTC day rollover.\n const dayBucket = Math.floor(Date.now() / 86_400_000) * 86_400_000;\n res.headers.set(\"Last-Modified\", new Date(dayBucket).toUTCString());\n res.headers.set(\"X-Smalk-Freshness\", \"bumped\");\n return res;\n}\n","export interface AdInput {\n pageUrl: string;\n userAgent: string;\n referer: string;\n clientIp: string;\n}\n\nexport interface AdResult {\n html: string;\n bookingId: string | null;\n}\n\nexport interface ApiResponse {\n html?: string;\n booking_id?: string;\n metadata?: Record<string, unknown>;\n}\n\nexport const DEFAULT_TIMEOUT_MS = 100;\nexport const DEFAULT_REVALIDATE_S = 1200;\nexport const API_PATH = '/transform/ads/content/';\n\nexport { getSmalkConfig, loadConfig } from './config';\nexport type { SmalkConfig, LegacySmalkConfig } from './config';\n\n/** @deprecated Use `SmalkConfig` from `./config`. Kept as alias for legacy imports. */\nexport type { LegacySmalkConfig as SmalkAdsConfig } from './config';\n\nexport { smalkFreshnessRevalidateHandler } from \"./freshness/route\";\nexport { smalkFreshnessMiddleware } from \"./freshness/middleware\";\nexport { cacheActiveUrls, readActiveUrls } from \"./freshness/activeUrlsCache\";\nexport {\n InMemoryActiveUrlsStore,\n FilesystemActiveUrlsStore,\n VercelKVActiveUrlsStore,\n CloudflareKVActiveUrlsStore,\n getActiveUrlsStore,\n setActiveUrlsStore,\n} from \"./freshness/activeUrlsStore\";\nexport type { ActiveUrlsStore, ActiveUrlsCachedPayload } from \"./freshness/activeUrlsStore\";\n"],"mappings":"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","names":["NextResponse","NextResponse"]}
|
|
@@ -2,13 +2,13 @@ import {
|
|
|
2
2
|
API_PATH,
|
|
3
3
|
DEFAULT_REVALIDATE_S,
|
|
4
4
|
DEFAULT_TIMEOUT_MS,
|
|
5
|
-
|
|
6
|
-
} from "./chunk-
|
|
5
|
+
getSmalkConfig
|
|
6
|
+
} from "./chunk-4QWOGJYF.js";
|
|
7
7
|
|
|
8
8
|
// src/client.ts
|
|
9
9
|
var lastHashByUrl = /* @__PURE__ */ new Map();
|
|
10
10
|
async function fetchAd(input, opts = {}) {
|
|
11
|
-
const cfg =
|
|
11
|
+
const cfg = getSmalkConfig();
|
|
12
12
|
if (!cfg.projectKey || !cfg.apiKey) return null;
|
|
13
13
|
const controller = new AbortController();
|
|
14
14
|
const timer = setTimeout(() => controller.abort(), DEFAULT_TIMEOUT_MS);
|
|
@@ -23,7 +23,7 @@ async function fetchAd(input, opts = {}) {
|
|
|
23
23
|
if (opts.preview !== void 0 && opts.preview !== false) {
|
|
24
24
|
body.preview = opts.preview;
|
|
25
25
|
}
|
|
26
|
-
const res = await fetch(`${cfg.
|
|
26
|
+
const res = await fetch(`${cfg.apiBase}${API_PATH}`, {
|
|
27
27
|
method: "POST",
|
|
28
28
|
headers: {
|
|
29
29
|
"Authorization": `Api-Key ${cfg.apiKey}`,
|
|
@@ -89,4 +89,4 @@ async function sha256Hex(input) {
|
|
|
89
89
|
export {
|
|
90
90
|
fetchAd
|
|
91
91
|
};
|
|
92
|
-
//# sourceMappingURL=chunk-
|
|
92
|
+
//# sourceMappingURL=chunk-INI3DN37.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/client.ts"],"sourcesContent":["import { getSmalkConfig } from './config';\nimport { AdInput, AdResult, ApiResponse, API_PATH, DEFAULT_TIMEOUT_MS, DEFAULT_REVALIDATE_S } from './index';\n\nconst lastHashByUrl = new Map<string, string>();\n\nexport function _resetHashCacheForTests(): void {\n lastHashByUrl.clear();\n}\n\nexport interface FetchAdOptions {\n revalidate?: number;\n /** Preview mode: `true` (PARAGRAPH placeholder) or a format string (e.g. `'FAQ'`). Bypasses booking lookup. */\n preview?: boolean | string;\n}\n\nexport async function fetchAd(input: AdInput, opts: FetchAdOptions = {}): Promise<AdResult | null> {\n const cfg = getSmalkConfig();\n if (!cfg.projectKey || !cfg.apiKey) return null;\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), DEFAULT_TIMEOUT_MS);\n\n try {\n const body: Record<string, unknown> = {\n project_key: cfg.projectKey,\n page_url: input.pageUrl,\n user_agent: input.userAgent,\n referer: input.referer,\n client_ip: input.clientIp,\n };\n if (opts.preview !== undefined && opts.preview !== false) {\n body.preview = opts.preview;\n }\n const res = await fetch(`${cfg.apiBase}${API_PATH}`, {\n method: 'POST',\n headers: {\n 'Authorization': `Api-Key ${cfg.apiKey}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(body),\n signal: controller.signal,\n // `next` is a Next.js fetch extension; type provided by next types when present.\n next: {\n revalidate: opts.revalidate ?? DEFAULT_REVALIDATE_S,\n tags: ['smalk-ad', `smalk-ad:${input.pageUrl}`],\n },\n } as RequestInit);\n\n if (!res.ok) {\n logErrorOnce(`smalk-ads fetch failed: HTTP ${res.status}`);\n return null;\n }\n\n const data = (await res.json()) as ApiResponse;\n if (!data.html) return null;\n\n const hash = await sha256Hex(data.html);\n const prev = lastHashByUrl.get(input.pageUrl);\n if (prev && prev !== hash) {\n await maybeRevalidatePath(input.pageUrl);\n }\n lastHashByUrl.set(input.pageUrl, hash);\n\n return { html: data.html, bookingId: data.booking_id ?? null };\n } catch (err) {\n if (err instanceof Error && err.name !== 'AbortError') {\n logErrorOnce(`smalk-ads fetch error: ${err.message}`);\n }\n return null;\n } finally {\n clearTimeout(timer);\n }\n}\n\nasync function maybeRevalidatePath(pageUrl: string): Promise<void> {\n try {\n const mod = await import('next/cache');\n if (typeof mod.revalidatePath === 'function') {\n const pathname = new URL(pageUrl).pathname;\n mod.revalidatePath(pathname);\n }\n } catch {\n // next/cache not available (Pages Router runtime, edge, or test env without next).\n }\n}\n\nconst seenErrors = new Set<string>();\nfunction logErrorOnce(msg: string): void {\n if (seenErrors.has(msg)) return;\n seenErrors.add(msg);\n console.error(`[smalk-nextjs-ads] ${msg}`);\n}\n\nasync function sha256Hex(input: string): Promise<string> {\n const data = new TextEncoder().encode(input);\n const buf = await globalThis.crypto.subtle.digest('SHA-256', data);\n const bytes = new Uint8Array(buf);\n let hex = '';\n for (let i = 0; i < bytes.length; i++) {\n hex += bytes[i].toString(16).padStart(2, '0');\n }\n return hex;\n}\n"],"mappings":";;;;;;;;AAGA,IAAM,gBAAgB,oBAAI,IAAoB;AAY9C,eAAsB,QAAQ,OAAgB,OAAuB,CAAC,GAA6B;AACjG,QAAM,MAAM,eAAe;AAC3B,MAAI,CAAC,IAAI,cAAc,CAAC,IAAI,OAAQ,QAAO;AAE3C,QAAM,aAAa,IAAI,gBAAgB;AACvC,QAAM,QAAQ,WAAW,MAAM,WAAW,MAAM,GAAG,kBAAkB;AAErE,MAAI;AACF,UAAM,OAAgC;AAAA,MACpC,aAAa,IAAI;AAAA,MACjB,UAAU,MAAM;AAAA,MAChB,YAAY,MAAM;AAAA,MAClB,SAAS,MAAM;AAAA,MACf,WAAW,MAAM;AAAA,IACnB;AACA,QAAI,KAAK,YAAY,UAAa,KAAK,YAAY,OAAO;AACxD,WAAK,UAAU,KAAK;AAAA,IACtB;AACA,UAAM,MAAM,MAAM,MAAM,GAAG,IAAI,OAAO,GAAG,QAAQ,IAAI;AAAA,MACnD,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,iBAAiB,WAAW,IAAI,MAAM;AAAA,QACtC,gBAAgB;AAAA,MAClB;AAAA,MACA,MAAM,KAAK,UAAU,IAAI;AAAA,MACzB,QAAQ,WAAW;AAAA;AAAA,MAEnB,MAAM;AAAA,QACJ,YAAY,KAAK,cAAc;AAAA,QAC/B,MAAM,CAAC,YAAY,YAAY,MAAM,OAAO,EAAE;AAAA,MAChD;AAAA,IACF,CAAgB;AAEhB,QAAI,CAAC,IAAI,IAAI;AACX,mBAAa,gCAAgC,IAAI,MAAM,EAAE;AACzD,aAAO;AAAA,IACT;AAEA,UAAM,OAAQ,MAAM,IAAI,KAAK;AAC7B,QAAI,CAAC,KAAK,KAAM,QAAO;AAEvB,UAAM,OAAO,MAAM,UAAU,KAAK,IAAI;AACtC,UAAM,OAAO,cAAc,IAAI,MAAM,OAAO;AAC5C,QAAI,QAAQ,SAAS,MAAM;AACzB,YAAM,oBAAoB,MAAM,OAAO;AAAA,IACzC;AACA,kBAAc,IAAI,MAAM,SAAS,IAAI;AAErC,WAAO,EAAE,MAAM,KAAK,MAAM,WAAW,KAAK,cAAc,KAAK;AAAA,EAC/D,SAAS,KAAK;AACZ,QAAI,eAAe,SAAS,IAAI,SAAS,cAAc;AACrD,mBAAa,0BAA0B,IAAI,OAAO,EAAE;AAAA,IACtD;AACA,WAAO;AAAA,EACT,UAAE;AACA,iBAAa,KAAK;AAAA,EACpB;AACF;AAEA,eAAe,oBAAoB,SAAgC;AACjE,MAAI;AACF,UAAM,MAAM,MAAM,OAAO,YAAY;AACrC,QAAI,OAAO,IAAI,mBAAmB,YAAY;AAC5C,YAAM,WAAW,IAAI,IAAI,OAAO,EAAE;AAClC,UAAI,eAAe,QAAQ;AAAA,IAC7B;AAAA,EACF,QAAQ;AAAA,EAER;AACF;AAEA,IAAM,aAAa,oBAAI,IAAY;AACnC,SAAS,aAAa,KAAmB;AACvC,MAAI,WAAW,IAAI,GAAG,EAAG;AACzB,aAAW,IAAI,GAAG;AAClB,UAAQ,MAAM,sBAAsB,GAAG,EAAE;AAC3C;AAEA,eAAe,UAAU,OAAgC;AACvD,QAAM,OAAO,IAAI,YAAY,EAAE,OAAO,KAAK;AAC3C,QAAM,MAAM,MAAM,WAAW,OAAO,OAAO,OAAO,WAAW,IAAI;AACjE,QAAM,QAAQ,IAAI,WAAW,GAAG;AAChC,MAAI,MAAM;AACV,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,WAAO,MAAM,CAAC,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG;AAAA,EAC9C;AACA,SAAO;AACT;","names":[]}
|
package/dist/index.cjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
8
|
var __export = (target, all) => {
|
|
7
9
|
for (var name in all)
|
|
@@ -15,44 +17,253 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
17
|
}
|
|
16
18
|
return to;
|
|
17
19
|
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
18
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
29
|
|
|
20
30
|
// src/index.ts
|
|
21
31
|
var src_exports = {};
|
|
22
32
|
__export(src_exports, {
|
|
23
33
|
API_PATH: () => API_PATH,
|
|
34
|
+
CloudflareKVActiveUrlsStore: () => CloudflareKVActiveUrlsStore,
|
|
24
35
|
DEFAULT_REVALIDATE_S: () => DEFAULT_REVALIDATE_S,
|
|
25
36
|
DEFAULT_TIMEOUT_MS: () => DEFAULT_TIMEOUT_MS,
|
|
26
|
-
|
|
37
|
+
FilesystemActiveUrlsStore: () => FilesystemActiveUrlsStore,
|
|
38
|
+
InMemoryActiveUrlsStore: () => InMemoryActiveUrlsStore,
|
|
39
|
+
VercelKVActiveUrlsStore: () => VercelKVActiveUrlsStore,
|
|
40
|
+
cacheActiveUrls: () => cacheActiveUrls,
|
|
41
|
+
getActiveUrlsStore: () => getActiveUrlsStore,
|
|
42
|
+
getSmalkConfig: () => getSmalkConfig,
|
|
43
|
+
loadConfig: () => loadConfig,
|
|
44
|
+
readActiveUrls: () => readActiveUrls,
|
|
45
|
+
setActiveUrlsStore: () => setActiveUrlsStore,
|
|
46
|
+
smalkFreshnessMiddleware: () => smalkFreshnessMiddleware,
|
|
47
|
+
smalkFreshnessRevalidateHandler: () => smalkFreshnessRevalidateHandler
|
|
27
48
|
});
|
|
28
49
|
module.exports = __toCommonJS(src_exports);
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
var
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const
|
|
35
|
-
const
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
console.warn("[smalk] SMALK_PROJECT_KEY not set \u2014 ad fetch will be skipped");
|
|
40
|
-
}
|
|
41
|
-
if (!apiKey) {
|
|
42
|
-
if (isProd) throw new Error("@smalk/nextjs-ads: SMALK_API_KEY env var is required in production");
|
|
43
|
-
console.warn("[smalk] SMALK_API_KEY not set \u2014 ad fetch will be skipped");
|
|
44
|
-
}
|
|
50
|
+
|
|
51
|
+
// src/config.ts
|
|
52
|
+
var DEFAULT_API_BASE = "https://api.smalk.ai/api/v1";
|
|
53
|
+
var PLUGIN_VERSION = "1.1.0";
|
|
54
|
+
function getSmalkConfig() {
|
|
55
|
+
const apiBase = process.env.SMALK_API_BASE || process.env.SMALK_API_BASE_URL || DEFAULT_API_BASE;
|
|
56
|
+
const apiKey = process.env.SMALK_API_KEY ?? "";
|
|
57
|
+
const projectKey = process.env.SMALK_PROJECT_KEY ?? "";
|
|
58
|
+
const cronSecret = process.env.SMALK_CRON_SECRET ?? "";
|
|
59
|
+
const publicHost = process.env.SMALK_PUBLIC_HOST || void 0;
|
|
45
60
|
return {
|
|
46
|
-
|
|
47
|
-
apiKey
|
|
48
|
-
|
|
61
|
+
apiBase,
|
|
62
|
+
apiKey,
|
|
63
|
+
projectKey,
|
|
64
|
+
cronSecret,
|
|
65
|
+
pluginVersion: PLUGIN_VERSION,
|
|
66
|
+
publicHost
|
|
49
67
|
};
|
|
50
68
|
}
|
|
69
|
+
function stripApiPathSuffix(apiBase) {
|
|
70
|
+
return apiBase.replace(/\/api\/v\d+\/?$/, "");
|
|
71
|
+
}
|
|
72
|
+
function loadConfig() {
|
|
73
|
+
const cfg = getSmalkConfig();
|
|
74
|
+
return { ...cfg, apiBaseUrl: stripApiPathSuffix(cfg.apiBase) };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// src/freshness/route.ts
|
|
78
|
+
var import_cache = require("next/cache");
|
|
79
|
+
var import_server = require("next/server");
|
|
80
|
+
|
|
81
|
+
// src/freshness/activeUrlsStore.ts
|
|
82
|
+
var InMemoryActiveUrlsStore = class {
|
|
83
|
+
value = null;
|
|
84
|
+
async read() {
|
|
85
|
+
return this.value;
|
|
86
|
+
}
|
|
87
|
+
async write(payload) {
|
|
88
|
+
this.value = payload;
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
var FilesystemActiveUrlsStore = class {
|
|
92
|
+
constructor(filePath) {
|
|
93
|
+
this.filePath = filePath;
|
|
94
|
+
}
|
|
95
|
+
filePath;
|
|
96
|
+
async read() {
|
|
97
|
+
const fs = await import("fs/promises");
|
|
98
|
+
const path = await import("path");
|
|
99
|
+
const os = await import("os");
|
|
100
|
+
const f = this.filePath || path.join(os.tmpdir(), "smalk-active-ad-paths.json");
|
|
101
|
+
try {
|
|
102
|
+
return JSON.parse(await fs.readFile(f, "utf8"));
|
|
103
|
+
} catch {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
async write(payload) {
|
|
108
|
+
const fs = await import("fs/promises");
|
|
109
|
+
const path = await import("path");
|
|
110
|
+
const os = await import("os");
|
|
111
|
+
const f = this.filePath || path.join(os.tmpdir(), "smalk-active-ad-paths.json");
|
|
112
|
+
await fs.mkdir(path.dirname(f), { recursive: true });
|
|
113
|
+
await fs.writeFile(f, JSON.stringify(payload));
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
var VERCEL_KV_MODULE = "@vercel/kv";
|
|
117
|
+
async function loadVercelKv() {
|
|
118
|
+
return await import(
|
|
119
|
+
/* @vite-ignore */
|
|
120
|
+
/* webpackIgnore: true */
|
|
121
|
+
VERCEL_KV_MODULE
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
var VercelKVActiveUrlsStore = class {
|
|
125
|
+
key;
|
|
126
|
+
constructor(key = "smalk:active-ad-paths") {
|
|
127
|
+
this.key = key;
|
|
128
|
+
}
|
|
129
|
+
async read() {
|
|
130
|
+
const { kv } = await loadVercelKv();
|
|
131
|
+
const raw = await kv.get(this.key);
|
|
132
|
+
if (!raw) return null;
|
|
133
|
+
try {
|
|
134
|
+
return typeof raw === "string" ? JSON.parse(raw) : raw;
|
|
135
|
+
} catch {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
async write(payload) {
|
|
140
|
+
const { kv } = await loadVercelKv();
|
|
141
|
+
await kv.set(this.key, JSON.stringify(payload));
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
var CloudflareKVActiveUrlsStore = class {
|
|
145
|
+
constructor(namespace, key = "smalk:active-ad-paths") {
|
|
146
|
+
this.namespace = namespace;
|
|
147
|
+
this.key = key;
|
|
148
|
+
}
|
|
149
|
+
namespace;
|
|
150
|
+
key;
|
|
151
|
+
async read() {
|
|
152
|
+
const raw = await this.namespace.get(this.key);
|
|
153
|
+
return raw ? JSON.parse(raw) : null;
|
|
154
|
+
}
|
|
155
|
+
async write(payload) {
|
|
156
|
+
await this.namespace.put(this.key, JSON.stringify(payload));
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
var currentStore = new FilesystemActiveUrlsStore();
|
|
160
|
+
function getActiveUrlsStore() {
|
|
161
|
+
return currentStore;
|
|
162
|
+
}
|
|
163
|
+
function setActiveUrlsStore(store) {
|
|
164
|
+
currentStore = store;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// src/freshness/activeUrlsCache.ts
|
|
168
|
+
async function cacheActiveUrls(paths) {
|
|
169
|
+
await getActiveUrlsStore().write({ paths, updated_at: Date.now() });
|
|
170
|
+
}
|
|
171
|
+
async function readActiveUrls() {
|
|
172
|
+
return getActiveUrlsStore().read();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// src/freshness/route.ts
|
|
176
|
+
async function smalkFreshnessRevalidateHandler(req) {
|
|
177
|
+
const cfg = getSmalkConfig();
|
|
178
|
+
const secret = req.headers.get("x-smalk-cron-secret");
|
|
179
|
+
if (!secret || secret !== cfg.cronSecret) {
|
|
180
|
+
return import_server.NextResponse.json({ error: "unauthorized" }, { status: 401 });
|
|
181
|
+
}
|
|
182
|
+
const endpoint = `${cfg.apiBase}/projects/${cfg.projectKey}/ads/inventory/active-ad-urls/`;
|
|
183
|
+
const resp = await fetch(endpoint, {
|
|
184
|
+
headers: {
|
|
185
|
+
"X-API-Key": cfg.apiKey,
|
|
186
|
+
"X-Smalk-CMS": "nextjs",
|
|
187
|
+
"X-Smalk-Plugin": cfg.pluginVersion
|
|
188
|
+
},
|
|
189
|
+
cache: "no-store"
|
|
190
|
+
});
|
|
191
|
+
if (!resp.ok) {
|
|
192
|
+
return import_server.NextResponse.json({ error: `api ${resp.status}` }, { status: 502 });
|
|
193
|
+
}
|
|
194
|
+
const payload = await resp.json();
|
|
195
|
+
const paths = payload.urls.map((u) => safePath(u.url, cfg.publicHost)).filter((p) => Boolean(p));
|
|
196
|
+
await cacheActiveUrls(paths);
|
|
197
|
+
let revalidated = 0;
|
|
198
|
+
for (const p of paths) {
|
|
199
|
+
try {
|
|
200
|
+
(0, import_cache.revalidatePath)(p);
|
|
201
|
+
revalidated++;
|
|
202
|
+
} catch (e) {
|
|
203
|
+
console.warn(`[smalk] revalidatePath failed for ${p}`, e);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return import_server.NextResponse.json({
|
|
207
|
+
ok: true,
|
|
208
|
+
total: paths.length,
|
|
209
|
+
revalidated,
|
|
210
|
+
ran_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
function safePath(url, publicHost) {
|
|
214
|
+
try {
|
|
215
|
+
const u = new URL(url);
|
|
216
|
+
if (publicHost && u.host !== publicHost) return null;
|
|
217
|
+
return u.pathname;
|
|
218
|
+
} catch {
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// src/freshness/middleware.ts
|
|
224
|
+
var import_server2 = require("next/server");
|
|
225
|
+
var pathsCache = null;
|
|
226
|
+
var pathsLoadedAt = 0;
|
|
227
|
+
var RELOAD_INTERVAL_MS = 6e4;
|
|
228
|
+
async function getPaths() {
|
|
229
|
+
const now = Date.now();
|
|
230
|
+
if (!pathsCache || now - pathsLoadedAt > RELOAD_INTERVAL_MS) {
|
|
231
|
+
const data = await readActiveUrls();
|
|
232
|
+
pathsCache = new Set(data?.paths ?? []);
|
|
233
|
+
pathsLoadedAt = now;
|
|
234
|
+
}
|
|
235
|
+
return pathsCache;
|
|
236
|
+
}
|
|
237
|
+
async function smalkFreshnessMiddleware(req) {
|
|
238
|
+
const paths = await getPaths();
|
|
239
|
+
if (!paths.has(req.nextUrl.pathname)) return void 0;
|
|
240
|
+
const res = import_server2.NextResponse.next();
|
|
241
|
+
const dayBucket = Math.floor(Date.now() / 864e5) * 864e5;
|
|
242
|
+
res.headers.set("Last-Modified", new Date(dayBucket).toUTCString());
|
|
243
|
+
res.headers.set("X-Smalk-Freshness", "bumped");
|
|
244
|
+
return res;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// src/index.ts
|
|
248
|
+
var DEFAULT_TIMEOUT_MS = 100;
|
|
249
|
+
var DEFAULT_REVALIDATE_S = 1200;
|
|
250
|
+
var API_PATH = "/transform/ads/content/";
|
|
51
251
|
// Annotate the CommonJS export names for ESM import in node:
|
|
52
252
|
0 && (module.exports = {
|
|
53
253
|
API_PATH,
|
|
254
|
+
CloudflareKVActiveUrlsStore,
|
|
54
255
|
DEFAULT_REVALIDATE_S,
|
|
55
256
|
DEFAULT_TIMEOUT_MS,
|
|
56
|
-
|
|
257
|
+
FilesystemActiveUrlsStore,
|
|
258
|
+
InMemoryActiveUrlsStore,
|
|
259
|
+
VercelKVActiveUrlsStore,
|
|
260
|
+
cacheActiveUrls,
|
|
261
|
+
getActiveUrlsStore,
|
|
262
|
+
getSmalkConfig,
|
|
263
|
+
loadConfig,
|
|
264
|
+
readActiveUrls,
|
|
265
|
+
setActiveUrlsStore,
|
|
266
|
+
smalkFreshnessMiddleware,
|
|
267
|
+
smalkFreshnessRevalidateHandler
|
|
57
268
|
});
|
|
58
269
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["export interface SmalkAdsConfig {\n projectKey: string;\n apiKey: string;\n apiBaseUrl: string;\n}\n\nexport interface AdInput {\n pageUrl: string;\n userAgent: string;\n referer: string;\n clientIp: string;\n}\n\nexport interface AdResult {\n html: string;\n bookingId: string | null;\n}\n\nexport interface ApiResponse {\n html?: string;\n booking_id?: string;\n metadata?: Record<string, unknown>;\n}\n\nexport const DEFAULT_TIMEOUT_MS = 100;\nexport const DEFAULT_REVALIDATE_S = 1200;\nexport const API_PATH = '/api/v1/transform/ads/content/';\n\nexport function loadConfig(): SmalkAdsConfig {\n const projectKey = process.env.SMALK_PROJECT_KEY;\n const apiKey = process.env.SMALK_API_KEY;\n const apiBaseUrl = process.env.SMALK_API_BASE_URL || 'https://api.smalk.ai';\n const isProd = process.env.NODE_ENV === 'production';\n\n if (!projectKey) {\n if (isProd) throw new Error('@smalk/nextjs-ads: SMALK_PROJECT_KEY env var is required in production');\n console.warn('[smalk] SMALK_PROJECT_KEY not set — ad fetch will be skipped');\n }\n if (!apiKey) {\n if (isProd) throw new Error('@smalk/nextjs-ads: SMALK_API_KEY env var is required in production');\n console.warn('[smalk] SMALK_API_KEY not set — ad fetch will be skipped');\n }\n\n return {\n projectKey: projectKey ?? '',\n apiKey: apiKey ?? '',\n apiBaseUrl,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAwBO,IAAM,qBAAqB;AAC3B,IAAM,uBAAuB;AAC7B,IAAM,WAAW;AAEjB,SAAS,aAA6B;AAC3C,QAAM,aAAa,QAAQ,IAAI;AAC/B,QAAM,SAAS,QAAQ,IAAI;AAC3B,QAAM,aAAa,QAAQ,IAAI,sBAAsB;AACrD,QAAM,SAAS,QAAQ,IAAI,aAAa;AAExC,MAAI,CAAC,YAAY;AACf,QAAI,OAAQ,OAAM,IAAI,MAAM,wEAAwE;AACpG,YAAQ,KAAK,mEAA8D;AAAA,EAC7E;AACA,MAAI,CAAC,QAAQ;AACX,QAAI,OAAQ,OAAM,IAAI,MAAM,oEAAoE;AAChG,YAAQ,KAAK,+DAA0D;AAAA,EACzE;AAEA,SAAO;AAAA,IACL,YAAY,cAAc;AAAA,IAC1B,QAAQ,UAAU;AAAA,IAClB;AAAA,EACF;AACF;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/config.ts","../src/freshness/route.ts","../src/freshness/activeUrlsStore.ts","../src/freshness/activeUrlsCache.ts","../src/freshness/middleware.ts"],"sourcesContent":["export interface AdInput {\n pageUrl: string;\n userAgent: string;\n referer: string;\n clientIp: string;\n}\n\nexport interface AdResult {\n html: string;\n bookingId: string | null;\n}\n\nexport interface ApiResponse {\n html?: string;\n booking_id?: string;\n metadata?: Record<string, unknown>;\n}\n\nexport const DEFAULT_TIMEOUT_MS = 100;\nexport const DEFAULT_REVALIDATE_S = 1200;\nexport const API_PATH = '/transform/ads/content/';\n\nexport { getSmalkConfig, loadConfig } from './config';\nexport type { SmalkConfig, LegacySmalkConfig } from './config';\n\n/** @deprecated Use `SmalkConfig` from `./config`. Kept as alias for legacy imports. */\nexport type { LegacySmalkConfig as SmalkAdsConfig } from './config';\n\nexport { smalkFreshnessRevalidateHandler } from \"./freshness/route\";\nexport { smalkFreshnessMiddleware } from \"./freshness/middleware\";\nexport { cacheActiveUrls, readActiveUrls } from \"./freshness/activeUrlsCache\";\nexport {\n InMemoryActiveUrlsStore,\n FilesystemActiveUrlsStore,\n VercelKVActiveUrlsStore,\n CloudflareKVActiveUrlsStore,\n getActiveUrlsStore,\n setActiveUrlsStore,\n} from \"./freshness/activeUrlsStore\";\nexport type { ActiveUrlsStore, ActiveUrlsCachedPayload } from \"./freshness/activeUrlsStore\";\n","export interface SmalkConfig {\n /** Full API base including version path, e.g. https://api.smalk.ai/api/v1 */\n apiBase: string;\n apiKey: string;\n projectKey: string;\n cronSecret: string;\n pluginVersion: string;\n publicHost?: string;\n}\n\n/**\n * Backwards-compatible config shape for legacy callers (pre-P2). Adds an\n * `apiBaseUrl` alias for `apiBase`. New code should use `getSmalkConfig()`\n * and read `apiBase` directly.\n */\nexport interface LegacySmalkConfig extends SmalkConfig {\n /**\n * @deprecated Use `apiBase` instead.\n *\n * Pre-P2.5 shape: host-only (e.g. `https://api.smalk.ai`), and external\n * publisher code appended `/api/v1/...`. To preserve that shape after the\n * P2.5 Next config unify (which moved `/api/v1` into the canonical\n * `apiBase`), this field strips a trailing `/api/vN` segment from\n * `apiBase` before exposing it. New code should read `apiBase` directly.\n */\n apiBaseUrl: string;\n}\n\nconst DEFAULT_API_BASE = \"https://api.smalk.ai/api/v1\";\nconst PLUGIN_VERSION = \"1.1.0\";\n\nexport function getSmalkConfig(): SmalkConfig {\n const apiBase = process.env.SMALK_API_BASE || process.env.SMALK_API_BASE_URL || DEFAULT_API_BASE;\n const apiKey = process.env.SMALK_API_KEY ?? \"\";\n const projectKey = process.env.SMALK_PROJECT_KEY ?? \"\";\n const cronSecret = process.env.SMALK_CRON_SECRET ?? \"\";\n const publicHost = process.env.SMALK_PUBLIC_HOST || undefined;\n\n return {\n apiBase,\n apiKey,\n projectKey,\n cronSecret,\n pluginVersion: PLUGIN_VERSION,\n publicHost,\n };\n}\n\n/**\n * Strip a trailing `/api/vN` (or `/api/vN/`) segment from an API base URL.\n * Preserves backward compatibility with publishers building URLs like\n * `${apiBaseUrl}/api/v1/...` — without stripping, those would double-prefix\n * to `/api/v1/api/v1/...` and 404.\n */\nfunction stripApiPathSuffix(apiBase: string): string {\n return apiBase.replace(/\\/api\\/v\\d+\\/?$/, \"\");\n}\n\n/**\n * Legacy loader retained for external consumers (publisher apps) that import\n * `loadConfig` from `@smalk/nextjs-ads`. Returns the canonical `SmalkConfig`\n * plus an `apiBaseUrl` alias whose shape matches the pre-P2.5 contract\n * (host-only, no `/api/v1` suffix). New code should prefer `getSmalkConfig()`\n * and read `apiBase` directly.\n */\nexport function loadConfig(): LegacySmalkConfig {\n const cfg = getSmalkConfig();\n return { ...cfg, apiBaseUrl: stripApiPathSuffix(cfg.apiBase) };\n}\n","import { revalidatePath } from \"next/cache\";\nimport { NextRequest, NextResponse } from \"next/server\";\n\nimport { getSmalkConfig } from \"../config\";\nimport { cacheActiveUrls } from \"./activeUrlsCache\";\n\nexport async function smalkFreshnessRevalidateHandler(req: NextRequest) {\n const cfg = getSmalkConfig();\n const secret = req.headers.get(\"x-smalk-cron-secret\");\n if (!secret || secret !== cfg.cronSecret) {\n return NextResponse.json({ error: \"unauthorized\" }, { status: 401 });\n }\n\n const endpoint = `${cfg.apiBase}/projects/${cfg.projectKey}/ads/inventory/active-ad-urls/`;\n const resp = await fetch(endpoint, {\n headers: {\n \"X-API-Key\": cfg.apiKey,\n \"X-Smalk-CMS\": \"nextjs\",\n \"X-Smalk-Plugin\": cfg.pluginVersion,\n },\n cache: \"no-store\",\n });\n if (!resp.ok) {\n return NextResponse.json({ error: `api ${resp.status}` }, { status: 502 });\n }\n const payload = (await resp.json()) as { urls: { url: string }[] };\n const paths = payload.urls\n .map((u) => safePath(u.url, cfg.publicHost))\n .filter((p): p is string => Boolean(p));\n\n await cacheActiveUrls(paths);\n\n let revalidated = 0;\n for (const p of paths) {\n try {\n revalidatePath(p);\n revalidated++;\n } catch (e) {\n console.warn(`[smalk] revalidatePath failed for ${p}`, e);\n }\n }\n\n return NextResponse.json({\n ok: true,\n total: paths.length,\n revalidated,\n ran_at: new Date().toISOString(),\n });\n}\n\nfunction safePath(url: string, publicHost?: string): string | null {\n try {\n const u = new URL(url);\n if (publicHost && u.host !== publicHost) return null;\n return u.pathname;\n } catch {\n return null;\n }\n}\n","export interface ActiveUrlsCachedPayload {\n paths: string[];\n updated_at: number;\n}\n\nexport interface ActiveUrlsStore {\n read(): Promise<ActiveUrlsCachedPayload | null>;\n write(payload: ActiveUrlsCachedPayload): Promise<void>;\n}\n\n// In-memory store (per-process, fast, lost on restart — good for tests).\nexport class InMemoryActiveUrlsStore implements ActiveUrlsStore {\n private value: ActiveUrlsCachedPayload | null = null;\n async read() { return this.value; }\n async write(payload: ActiveUrlsCachedPayload) { this.value = payload; }\n}\n\n// Filesystem fallback — uses os.tmpdir() (current behavior, kept for Node dev).\nexport class FilesystemActiveUrlsStore implements ActiveUrlsStore {\n constructor(private filePath?: string) {}\n async read(): Promise<ActiveUrlsCachedPayload | null> {\n const fs = await import(\"node:fs/promises\");\n const path = await import(\"node:path\");\n const os = await import(\"node:os\");\n const f = this.filePath || path.join(os.tmpdir(), \"smalk-active-ad-paths.json\");\n try {\n return JSON.parse(await fs.readFile(f, \"utf8\"));\n } catch {\n return null;\n }\n }\n async write(payload: ActiveUrlsCachedPayload) {\n const fs = await import(\"node:fs/promises\");\n const path = await import(\"node:path\");\n const os = await import(\"node:os\");\n const f = this.filePath || path.join(os.tmpdir(), \"smalk-active-ad-paths.json\");\n await fs.mkdir(path.dirname(f), { recursive: true });\n await fs.writeFile(f, JSON.stringify(payload));\n }\n}\n\n// Vercel KV adapter — requires @vercel/kv installed by the publisher.\n// We don't bundle the dep; lazy-import via a non-literal specifier so\n// bundlers (vite/vitest/tsup/webpack) don't try to resolve it at build time.\nconst VERCEL_KV_MODULE = \"@vercel/kv\";\nasync function loadVercelKv(): Promise<{ kv: { get(k: string): Promise<unknown>; set(k: string, v: string): Promise<unknown> } }> {\n // Indirect specifier — opaque to bundler static analysis.\n return (await import(/* @vite-ignore */ /* webpackIgnore: true */ VERCEL_KV_MODULE)) as {\n kv: { get(k: string): Promise<unknown>; set(k: string, v: string): Promise<unknown> };\n };\n}\n\nexport class VercelKVActiveUrlsStore implements ActiveUrlsStore {\n private readonly key: string;\n constructor(key = \"smalk:active-ad-paths\") { this.key = key; }\n async read(): Promise<ActiveUrlsCachedPayload | null> {\n const { kv } = await loadVercelKv();\n const raw = await kv.get(this.key);\n if (!raw) return null;\n try { return typeof raw === \"string\" ? JSON.parse(raw) : (raw as ActiveUrlsCachedPayload); }\n catch { return null; }\n }\n async write(payload: ActiveUrlsCachedPayload) {\n const { kv } = await loadVercelKv();\n await kv.set(this.key, JSON.stringify(payload));\n }\n}\n\n// Cloudflare KV adapter — publisher passes the KVNamespace binding.\nexport class CloudflareKVActiveUrlsStore implements ActiveUrlsStore {\n constructor(private namespace: { get(key: string): Promise<string | null>; put(key: string, value: string): Promise<void> }, private key = \"smalk:active-ad-paths\") {}\n async read(): Promise<ActiveUrlsCachedPayload | null> {\n const raw = await this.namespace.get(this.key);\n return raw ? JSON.parse(raw) : null;\n }\n async write(payload: ActiveUrlsCachedPayload) {\n await this.namespace.put(this.key, JSON.stringify(payload));\n }\n}\n\n// Module-level default — process-wide singleton, swap with setActiveUrlsStore().\nlet currentStore: ActiveUrlsStore = new FilesystemActiveUrlsStore();\n\nexport function getActiveUrlsStore(): ActiveUrlsStore {\n return currentStore;\n}\n\nexport function setActiveUrlsStore(store: ActiveUrlsStore): void {\n currentStore = store;\n}\n","import { getActiveUrlsStore, type ActiveUrlsCachedPayload } from \"./activeUrlsStore\";\n\nexport async function cacheActiveUrls(paths: string[]) {\n await getActiveUrlsStore().write({ paths, updated_at: Date.now() });\n}\n\nexport async function readActiveUrls(): Promise<ActiveUrlsCachedPayload | null> {\n return getActiveUrlsStore().read();\n}\n\n// Re-export for publishers configuring their own store\nexport { getActiveUrlsStore, setActiveUrlsStore } from \"./activeUrlsStore\";\nexport type { ActiveUrlsStore, ActiveUrlsCachedPayload } from \"./activeUrlsStore\";\n","import { NextRequest, NextResponse } from \"next/server\";\n\nimport { readActiveUrls } from \"./activeUrlsCache\";\n\nlet pathsCache: Set<string> | null = null;\nlet pathsLoadedAt = 0;\nconst RELOAD_INTERVAL_MS = 60_000;\n\nasync function getPaths(): Promise<Set<string>> {\n const now = Date.now();\n if (!pathsCache || now - pathsLoadedAt > RELOAD_INTERVAL_MS) {\n const data = await readActiveUrls();\n pathsCache = new Set(data?.paths ?? []);\n pathsLoadedAt = now;\n }\n return pathsCache;\n}\n\nexport async function smalkFreshnessMiddleware(req: NextRequest): Promise<NextResponse | undefined> {\n const paths = await getPaths();\n if (!paths.has(req.nextUrl.pathname)) return undefined;\n const res = NextResponse.next();\n // Day-bucket stable Last-Modified so real users with If-Modified-Since\n // still get 304s within the day; AI crawlers (which hit infrequently)\n // get a fresh signal on the next UTC day rollover.\n const dayBucket = Math.floor(Date.now() / 86_400_000) * 86_400_000;\n res.headers.set(\"Last-Modified\", new Date(dayBucket).toUTCString());\n res.headers.set(\"X-Smalk-Freshness\", \"bumped\");\n return res;\n}\n"],"mappings":"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","names":["import_server"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,8 +1,93 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
|
|
3
|
+
interface SmalkConfig {
|
|
4
|
+
/** Full API base including version path, e.g. https://api.smalk.ai/api/v1 */
|
|
5
|
+
apiBase: string;
|
|
3
6
|
apiKey: string;
|
|
7
|
+
projectKey: string;
|
|
8
|
+
cronSecret: string;
|
|
9
|
+
pluginVersion: string;
|
|
10
|
+
publicHost?: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Backwards-compatible config shape for legacy callers (pre-P2). Adds an
|
|
14
|
+
* `apiBaseUrl` alias for `apiBase`. New code should use `getSmalkConfig()`
|
|
15
|
+
* and read `apiBase` directly.
|
|
16
|
+
*/
|
|
17
|
+
interface LegacySmalkConfig extends SmalkConfig {
|
|
18
|
+
/**
|
|
19
|
+
* @deprecated Use `apiBase` instead.
|
|
20
|
+
*
|
|
21
|
+
* Pre-P2.5 shape: host-only (e.g. `https://api.smalk.ai`), and external
|
|
22
|
+
* publisher code appended `/api/v1/...`. To preserve that shape after the
|
|
23
|
+
* P2.5 Next config unify (which moved `/api/v1` into the canonical
|
|
24
|
+
* `apiBase`), this field strips a trailing `/api/vN` segment from
|
|
25
|
+
* `apiBase` before exposing it. New code should read `apiBase` directly.
|
|
26
|
+
*/
|
|
4
27
|
apiBaseUrl: string;
|
|
5
28
|
}
|
|
29
|
+
declare function getSmalkConfig(): SmalkConfig;
|
|
30
|
+
/**
|
|
31
|
+
* Legacy loader retained for external consumers (publisher apps) that import
|
|
32
|
+
* `loadConfig` from `@smalk/nextjs-ads`. Returns the canonical `SmalkConfig`
|
|
33
|
+
* plus an `apiBaseUrl` alias whose shape matches the pre-P2.5 contract
|
|
34
|
+
* (host-only, no `/api/v1` suffix). New code should prefer `getSmalkConfig()`
|
|
35
|
+
* and read `apiBase` directly.
|
|
36
|
+
*/
|
|
37
|
+
declare function loadConfig(): LegacySmalkConfig;
|
|
38
|
+
|
|
39
|
+
declare function smalkFreshnessRevalidateHandler(req: NextRequest): Promise<NextResponse<{
|
|
40
|
+
error: string;
|
|
41
|
+
}> | NextResponse<{
|
|
42
|
+
ok: boolean;
|
|
43
|
+
total: number;
|
|
44
|
+
revalidated: number;
|
|
45
|
+
ran_at: string;
|
|
46
|
+
}>>;
|
|
47
|
+
|
|
48
|
+
declare function smalkFreshnessMiddleware(req: NextRequest): Promise<NextResponse | undefined>;
|
|
49
|
+
|
|
50
|
+
interface ActiveUrlsCachedPayload {
|
|
51
|
+
paths: string[];
|
|
52
|
+
updated_at: number;
|
|
53
|
+
}
|
|
54
|
+
interface ActiveUrlsStore {
|
|
55
|
+
read(): Promise<ActiveUrlsCachedPayload | null>;
|
|
56
|
+
write(payload: ActiveUrlsCachedPayload): Promise<void>;
|
|
57
|
+
}
|
|
58
|
+
declare class InMemoryActiveUrlsStore implements ActiveUrlsStore {
|
|
59
|
+
private value;
|
|
60
|
+
read(): Promise<ActiveUrlsCachedPayload | null>;
|
|
61
|
+
write(payload: ActiveUrlsCachedPayload): Promise<void>;
|
|
62
|
+
}
|
|
63
|
+
declare class FilesystemActiveUrlsStore implements ActiveUrlsStore {
|
|
64
|
+
private filePath?;
|
|
65
|
+
constructor(filePath?: string | undefined);
|
|
66
|
+
read(): Promise<ActiveUrlsCachedPayload | null>;
|
|
67
|
+
write(payload: ActiveUrlsCachedPayload): Promise<void>;
|
|
68
|
+
}
|
|
69
|
+
declare class VercelKVActiveUrlsStore implements ActiveUrlsStore {
|
|
70
|
+
private readonly key;
|
|
71
|
+
constructor(key?: string);
|
|
72
|
+
read(): Promise<ActiveUrlsCachedPayload | null>;
|
|
73
|
+
write(payload: ActiveUrlsCachedPayload): Promise<void>;
|
|
74
|
+
}
|
|
75
|
+
declare class CloudflareKVActiveUrlsStore implements ActiveUrlsStore {
|
|
76
|
+
private namespace;
|
|
77
|
+
private key;
|
|
78
|
+
constructor(namespace: {
|
|
79
|
+
get(key: string): Promise<string | null>;
|
|
80
|
+
put(key: string, value: string): Promise<void>;
|
|
81
|
+
}, key?: string);
|
|
82
|
+
read(): Promise<ActiveUrlsCachedPayload | null>;
|
|
83
|
+
write(payload: ActiveUrlsCachedPayload): Promise<void>;
|
|
84
|
+
}
|
|
85
|
+
declare function getActiveUrlsStore(): ActiveUrlsStore;
|
|
86
|
+
declare function setActiveUrlsStore(store: ActiveUrlsStore): void;
|
|
87
|
+
|
|
88
|
+
declare function cacheActiveUrls(paths: string[]): Promise<void>;
|
|
89
|
+
declare function readActiveUrls(): Promise<ActiveUrlsCachedPayload | null>;
|
|
90
|
+
|
|
6
91
|
interface AdInput {
|
|
7
92
|
pageUrl: string;
|
|
8
93
|
userAgent: string;
|
|
@@ -20,7 +105,6 @@ interface ApiResponse {
|
|
|
20
105
|
}
|
|
21
106
|
declare const DEFAULT_TIMEOUT_MS = 100;
|
|
22
107
|
declare const DEFAULT_REVALIDATE_S = 1200;
|
|
23
|
-
declare const API_PATH = "/
|
|
24
|
-
declare function loadConfig(): SmalkAdsConfig;
|
|
108
|
+
declare const API_PATH = "/transform/ads/content/";
|
|
25
109
|
|
|
26
|
-
export { API_PATH, type AdInput, type AdResult, type ApiResponse, DEFAULT_REVALIDATE_S, DEFAULT_TIMEOUT_MS, type SmalkAdsConfig, loadConfig };
|
|
110
|
+
export { API_PATH, type ActiveUrlsCachedPayload, type ActiveUrlsStore, type AdInput, type AdResult, type ApiResponse, CloudflareKVActiveUrlsStore, DEFAULT_REVALIDATE_S, DEFAULT_TIMEOUT_MS, FilesystemActiveUrlsStore, InMemoryActiveUrlsStore, type LegacySmalkConfig, type LegacySmalkConfig as SmalkAdsConfig, type SmalkConfig, VercelKVActiveUrlsStore, cacheActiveUrls, getActiveUrlsStore, getSmalkConfig, loadConfig, readActiveUrls, setActiveUrlsStore, smalkFreshnessMiddleware, smalkFreshnessRevalidateHandler };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,93 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
|
|
3
|
+
interface SmalkConfig {
|
|
4
|
+
/** Full API base including version path, e.g. https://api.smalk.ai/api/v1 */
|
|
5
|
+
apiBase: string;
|
|
3
6
|
apiKey: string;
|
|
7
|
+
projectKey: string;
|
|
8
|
+
cronSecret: string;
|
|
9
|
+
pluginVersion: string;
|
|
10
|
+
publicHost?: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Backwards-compatible config shape for legacy callers (pre-P2). Adds an
|
|
14
|
+
* `apiBaseUrl` alias for `apiBase`. New code should use `getSmalkConfig()`
|
|
15
|
+
* and read `apiBase` directly.
|
|
16
|
+
*/
|
|
17
|
+
interface LegacySmalkConfig extends SmalkConfig {
|
|
18
|
+
/**
|
|
19
|
+
* @deprecated Use `apiBase` instead.
|
|
20
|
+
*
|
|
21
|
+
* Pre-P2.5 shape: host-only (e.g. `https://api.smalk.ai`), and external
|
|
22
|
+
* publisher code appended `/api/v1/...`. To preserve that shape after the
|
|
23
|
+
* P2.5 Next config unify (which moved `/api/v1` into the canonical
|
|
24
|
+
* `apiBase`), this field strips a trailing `/api/vN` segment from
|
|
25
|
+
* `apiBase` before exposing it. New code should read `apiBase` directly.
|
|
26
|
+
*/
|
|
4
27
|
apiBaseUrl: string;
|
|
5
28
|
}
|
|
29
|
+
declare function getSmalkConfig(): SmalkConfig;
|
|
30
|
+
/**
|
|
31
|
+
* Legacy loader retained for external consumers (publisher apps) that import
|
|
32
|
+
* `loadConfig` from `@smalk/nextjs-ads`. Returns the canonical `SmalkConfig`
|
|
33
|
+
* plus an `apiBaseUrl` alias whose shape matches the pre-P2.5 contract
|
|
34
|
+
* (host-only, no `/api/v1` suffix). New code should prefer `getSmalkConfig()`
|
|
35
|
+
* and read `apiBase` directly.
|
|
36
|
+
*/
|
|
37
|
+
declare function loadConfig(): LegacySmalkConfig;
|
|
38
|
+
|
|
39
|
+
declare function smalkFreshnessRevalidateHandler(req: NextRequest): Promise<NextResponse<{
|
|
40
|
+
error: string;
|
|
41
|
+
}> | NextResponse<{
|
|
42
|
+
ok: boolean;
|
|
43
|
+
total: number;
|
|
44
|
+
revalidated: number;
|
|
45
|
+
ran_at: string;
|
|
46
|
+
}>>;
|
|
47
|
+
|
|
48
|
+
declare function smalkFreshnessMiddleware(req: NextRequest): Promise<NextResponse | undefined>;
|
|
49
|
+
|
|
50
|
+
interface ActiveUrlsCachedPayload {
|
|
51
|
+
paths: string[];
|
|
52
|
+
updated_at: number;
|
|
53
|
+
}
|
|
54
|
+
interface ActiveUrlsStore {
|
|
55
|
+
read(): Promise<ActiveUrlsCachedPayload | null>;
|
|
56
|
+
write(payload: ActiveUrlsCachedPayload): Promise<void>;
|
|
57
|
+
}
|
|
58
|
+
declare class InMemoryActiveUrlsStore implements ActiveUrlsStore {
|
|
59
|
+
private value;
|
|
60
|
+
read(): Promise<ActiveUrlsCachedPayload | null>;
|
|
61
|
+
write(payload: ActiveUrlsCachedPayload): Promise<void>;
|
|
62
|
+
}
|
|
63
|
+
declare class FilesystemActiveUrlsStore implements ActiveUrlsStore {
|
|
64
|
+
private filePath?;
|
|
65
|
+
constructor(filePath?: string | undefined);
|
|
66
|
+
read(): Promise<ActiveUrlsCachedPayload | null>;
|
|
67
|
+
write(payload: ActiveUrlsCachedPayload): Promise<void>;
|
|
68
|
+
}
|
|
69
|
+
declare class VercelKVActiveUrlsStore implements ActiveUrlsStore {
|
|
70
|
+
private readonly key;
|
|
71
|
+
constructor(key?: string);
|
|
72
|
+
read(): Promise<ActiveUrlsCachedPayload | null>;
|
|
73
|
+
write(payload: ActiveUrlsCachedPayload): Promise<void>;
|
|
74
|
+
}
|
|
75
|
+
declare class CloudflareKVActiveUrlsStore implements ActiveUrlsStore {
|
|
76
|
+
private namespace;
|
|
77
|
+
private key;
|
|
78
|
+
constructor(namespace: {
|
|
79
|
+
get(key: string): Promise<string | null>;
|
|
80
|
+
put(key: string, value: string): Promise<void>;
|
|
81
|
+
}, key?: string);
|
|
82
|
+
read(): Promise<ActiveUrlsCachedPayload | null>;
|
|
83
|
+
write(payload: ActiveUrlsCachedPayload): Promise<void>;
|
|
84
|
+
}
|
|
85
|
+
declare function getActiveUrlsStore(): ActiveUrlsStore;
|
|
86
|
+
declare function setActiveUrlsStore(store: ActiveUrlsStore): void;
|
|
87
|
+
|
|
88
|
+
declare function cacheActiveUrls(paths: string[]): Promise<void>;
|
|
89
|
+
declare function readActiveUrls(): Promise<ActiveUrlsCachedPayload | null>;
|
|
90
|
+
|
|
6
91
|
interface AdInput {
|
|
7
92
|
pageUrl: string;
|
|
8
93
|
userAgent: string;
|
|
@@ -20,7 +105,6 @@ interface ApiResponse {
|
|
|
20
105
|
}
|
|
21
106
|
declare const DEFAULT_TIMEOUT_MS = 100;
|
|
22
107
|
declare const DEFAULT_REVALIDATE_S = 1200;
|
|
23
|
-
declare const API_PATH = "/
|
|
24
|
-
declare function loadConfig(): SmalkAdsConfig;
|
|
108
|
+
declare const API_PATH = "/transform/ads/content/";
|
|
25
109
|
|
|
26
|
-
export { API_PATH, type AdInput, type AdResult, type ApiResponse, DEFAULT_REVALIDATE_S, DEFAULT_TIMEOUT_MS, type SmalkAdsConfig, loadConfig };
|
|
110
|
+
export { API_PATH, type ActiveUrlsCachedPayload, type ActiveUrlsStore, type AdInput, type AdResult, type ApiResponse, CloudflareKVActiveUrlsStore, DEFAULT_REVALIDATE_S, DEFAULT_TIMEOUT_MS, FilesystemActiveUrlsStore, InMemoryActiveUrlsStore, type LegacySmalkConfig, type LegacySmalkConfig as SmalkAdsConfig, type SmalkConfig, VercelKVActiveUrlsStore, cacheActiveUrls, getActiveUrlsStore, getSmalkConfig, loadConfig, readActiveUrls, setActiveUrlsStore, smalkFreshnessMiddleware, smalkFreshnessRevalidateHandler };
|
package/dist/index.js
CHANGED
|
@@ -1,13 +1,35 @@
|
|
|
1
1
|
import {
|
|
2
2
|
API_PATH,
|
|
3
|
+
CloudflareKVActiveUrlsStore,
|
|
3
4
|
DEFAULT_REVALIDATE_S,
|
|
4
5
|
DEFAULT_TIMEOUT_MS,
|
|
5
|
-
|
|
6
|
-
|
|
6
|
+
FilesystemActiveUrlsStore,
|
|
7
|
+
InMemoryActiveUrlsStore,
|
|
8
|
+
VercelKVActiveUrlsStore,
|
|
9
|
+
cacheActiveUrls,
|
|
10
|
+
getActiveUrlsStore,
|
|
11
|
+
getSmalkConfig,
|
|
12
|
+
loadConfig,
|
|
13
|
+
readActiveUrls,
|
|
14
|
+
setActiveUrlsStore,
|
|
15
|
+
smalkFreshnessMiddleware,
|
|
16
|
+
smalkFreshnessRevalidateHandler
|
|
17
|
+
} from "./chunk-4QWOGJYF.js";
|
|
7
18
|
export {
|
|
8
19
|
API_PATH,
|
|
20
|
+
CloudflareKVActiveUrlsStore,
|
|
9
21
|
DEFAULT_REVALIDATE_S,
|
|
10
22
|
DEFAULT_TIMEOUT_MS,
|
|
11
|
-
|
|
23
|
+
FilesystemActiveUrlsStore,
|
|
24
|
+
InMemoryActiveUrlsStore,
|
|
25
|
+
VercelKVActiveUrlsStore,
|
|
26
|
+
cacheActiveUrls,
|
|
27
|
+
getActiveUrlsStore,
|
|
28
|
+
getSmalkConfig,
|
|
29
|
+
loadConfig,
|
|
30
|
+
readActiveUrls,
|
|
31
|
+
setActiveUrlsStore,
|
|
32
|
+
smalkFreshnessMiddleware,
|
|
33
|
+
smalkFreshnessRevalidateHandler
|
|
12
34
|
};
|
|
13
35
|
//# sourceMappingURL=index.js.map
|