apptvty 0.3.1 → 0.4.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/dist/{chunk-RNZYGUMF.mjs → chunk-2UCECX7X.mjs} +65 -36
- package/dist/chunk-2UCECX7X.mjs.map +1 -0
- package/dist/{chunk-DFSZAE6R.mjs → chunk-INZJVNUI.mjs} +43 -20
- package/dist/chunk-INZJVNUI.mjs.map +1 -0
- package/dist/{chunk-EZYPAN7G.mjs → chunk-LHLZDTTY.mjs} +28 -2
- package/dist/chunk-LHLZDTTY.mjs.map +1 -0
- package/dist/cli.js +66 -1
- package/dist/index.d.mts +16 -2
- package/dist/index.d.ts +16 -2
- package/dist/index.js +133 -55
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3 -3
- package/dist/middleware/express.d.mts +1 -1
- package/dist/middleware/express.d.ts +1 -1
- package/dist/middleware/express.js +68 -19
- package/dist/middleware/express.js.map +1 -1
- package/dist/middleware/express.mjs +2 -2
- package/dist/middleware/nextjs.d.mts +1 -1
- package/dist/middleware/nextjs.d.ts +1 -1
- package/dist/middleware/nextjs.js +90 -35
- package/dist/middleware/nextjs.js.map +1 -1
- package/dist/middleware/nextjs.mjs +2 -2
- package/dist/setup.d.mts +1 -1
- package/dist/setup.d.ts +1 -1
- package/dist/{types-DwAWwqqD.d.mts → types-Bz3fBGpw.d.mts} +6 -1
- package/dist/{types-DwAWwqqD.d.ts → types-Bz3fBGpw.d.ts} +6 -1
- package/package.json +1 -1
- package/dist/chunk-DFSZAE6R.mjs.map +0 -1
- package/dist/chunk-EZYPAN7G.mjs.map +0 -1
- package/dist/chunk-RNZYGUMF.mjs.map +0 -1
|
@@ -108,6 +108,28 @@ var ApptvtyClient = class {
|
|
|
108
108
|
this.warn("Failed to log impression (billing may be delayed):", err);
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
|
+
// ─── Programmatic Indexing ───────────────────────────────────────────────────
|
|
112
|
+
/**
|
|
113
|
+
* Manually push text or markdown directly into your site's RAG vector database.
|
|
114
|
+
* This instantly embeds the content for AI agents to query, bypassing the
|
|
115
|
+
* need for a scheduled HTML crawl. This is ideal for Client-Side Rendered (CSR)
|
|
116
|
+
* Apps or for injecting private institutional facts/databases.
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
* const result = await client.pushKnowledge({
|
|
120
|
+
* title: "Capitol Technology University Profile",
|
|
121
|
+
* content: "Here are all the detailed specs, majors, and acceptances...",
|
|
122
|
+
* url: "https://thecollegeradar.com/institution/499" // Optional reference
|
|
123
|
+
* });
|
|
124
|
+
*/
|
|
125
|
+
async pushKnowledge(params) {
|
|
126
|
+
const payload = {
|
|
127
|
+
title: params.title,
|
|
128
|
+
content: params.content,
|
|
129
|
+
url: params.url
|
|
130
|
+
};
|
|
131
|
+
return this.post(`/v1/sites/${this.siteId}/knowledge`, payload);
|
|
132
|
+
}
|
|
111
133
|
// ─── Analytics (for coding agents) ───────────────────────────────────────────
|
|
112
134
|
// These allow agents to check activity, logs, and errors without a human.
|
|
113
135
|
/** Get 30-day traffic overview (requests, AI %, crawlers, queries). */
|
|
@@ -355,8 +377,12 @@ var ApptvtyClient = class {
|
|
|
355
377
|
let dashboardUrl = (typeof process !== "undefined" ? process.env?.DASHBOARD_URL : void 0) ?? "https://dashboard.apptvty.com/login";
|
|
356
378
|
try {
|
|
357
379
|
const json = JSON.parse(text);
|
|
380
|
+
if (json?.error?.code === "INSUFFICIENT_BALANCE") {
|
|
381
|
+
throw new ApptvtyApiError(402, path, text);
|
|
382
|
+
}
|
|
358
383
|
dashboardUrl = json?.error?.details?.dashboard_url ?? dashboardUrl;
|
|
359
|
-
} catch {
|
|
384
|
+
} catch (e) {
|
|
385
|
+
if (e instanceof ApptvtyApiError) throw e;
|
|
360
386
|
}
|
|
361
387
|
throw new ApptvtyTrialExpiredError(dashboardUrl);
|
|
362
388
|
}
|
|
@@ -1166,13 +1192,18 @@ function createExpressMiddleware(config) {
|
|
|
1166
1192
|
const crawlerInfo = detectCrawler(userAgent);
|
|
1167
1193
|
const scraperService = detectScraperService(userAgent);
|
|
1168
1194
|
const path = req.url ?? "/";
|
|
1169
|
-
const isCrawler = crawlerInfo.isAi || scraperService.isScraperService;
|
|
1170
1195
|
const ipAddress = getClientIp(req.headers);
|
|
1171
|
-
const
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1196
|
+
const urlObj = new URL(path, `http://${req.headers.host ?? "localhost"}`);
|
|
1197
|
+
const aiCrawlerParam = parseBoolParam(urlObj.searchParams.get("ai_crawler"), false);
|
|
1198
|
+
const attributionId = urlObj.searchParams.get("atid");
|
|
1199
|
+
const isAi = crawlerInfo.isAi || aiCrawlerParam;
|
|
1200
|
+
const isScraper = isAi || scraperService.isScraperService || crawlerInfo.name === "unknown_bot";
|
|
1201
|
+
const adsPromise = !isInternalRequest(req) && !shouldSkip(path) ? client.getAdsForPage({ site_id: config.siteId, page_path: path }).catch(() => ({ ads: [] })) : Promise.resolve({ ads: [] });
|
|
1202
|
+
const chunks = [];
|
|
1203
|
+
const originalWrite = res.write.bind(res);
|
|
1204
|
+
const originalEnd = res.end.bind(res);
|
|
1205
|
+
const shouldBuffer = !isInternalRequest(req) && !shouldSkip(path);
|
|
1206
|
+
if (shouldBuffer) {
|
|
1176
1207
|
res.write = function(chunk, encodingOrCallback, callback) {
|
|
1177
1208
|
if (chunk != null) {
|
|
1178
1209
|
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
@@ -1187,17 +1218,12 @@ function createExpressMiddleware(config) {
|
|
|
1187
1218
|
}
|
|
1188
1219
|
const contentType = res.getHeader("content-type") ?? "";
|
|
1189
1220
|
const isHtml = contentType.includes("text/html");
|
|
1190
|
-
if (!isHtml || chunks.length === 0) {
|
|
1221
|
+
if (!isHtml || chunks.length === 0 || res.statusCode !== 200) {
|
|
1191
1222
|
res.write = originalWrite;
|
|
1192
1223
|
res.end = originalEnd;
|
|
1193
1224
|
return originalEnd(Buffer.concat(chunks), encodingOrCallback, callback);
|
|
1194
1225
|
}
|
|
1195
1226
|
const html = Buffer.concat(chunks).toString("utf-8");
|
|
1196
|
-
if (html.includes(AD_INJECTION_MARKER)) {
|
|
1197
|
-
res.write = originalWrite;
|
|
1198
|
-
res.end = originalEnd;
|
|
1199
|
-
return originalEnd(html, encodingOrCallback, callback);
|
|
1200
|
-
}
|
|
1201
1227
|
adsPromise.then((pageAds) => {
|
|
1202
1228
|
res.write = originalWrite;
|
|
1203
1229
|
res.end = originalEnd;
|
|
@@ -1205,14 +1231,33 @@ function createExpressMiddleware(config) {
|
|
|
1205
1231
|
originalEnd(html, encodingOrCallback, callback);
|
|
1206
1232
|
return;
|
|
1207
1233
|
}
|
|
1208
|
-
const
|
|
1234
|
+
const ad = pageAds.ads[0];
|
|
1235
|
+
let modified = html;
|
|
1236
|
+
if (isAi || scraperService.isScraperService) {
|
|
1237
|
+
modified = injectIntoHtml(html, pageAds.ads, scraperService.isScraperService);
|
|
1238
|
+
} else {
|
|
1239
|
+
const jsonLd = `
|
|
1240
|
+
<script type="application/ld+json">{"@context":"https://schema.org","@type":"CreativeWork","author":{"@type":"Organization","name":"${ad.advertiser}"},"mainEntityOfPage":{"@type":"WebPage","@id":"${ad.url}"},"headline":"Sponsored: ${ad.text}"}</script>
|
|
1241
|
+
`;
|
|
1242
|
+
const stealthDiv = `
|
|
1243
|
+
<div style="display:none !important;visibility:hidden;height:0;width:0;overflow:hidden;" aria-hidden="true" data-apptvty-ad="${ad.impression_id}">Sponsored by ${ad.advertiser}: <a href="${ad.url}">${ad.text}</a></div>
|
|
1244
|
+
`;
|
|
1245
|
+
if (html.includes("</head>")) {
|
|
1246
|
+
modified = html.replace("</head>", `${jsonLd}</head>`);
|
|
1247
|
+
}
|
|
1248
|
+
if (modified.includes("</body>")) {
|
|
1249
|
+
modified = modified.replace("</body>", `${stealthDiv}</body>`);
|
|
1250
|
+
} else {
|
|
1251
|
+
modified += stealthDiv;
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1209
1254
|
res.setHeader("X-Sponsored-Content", buildSponsoredHeader(pageAds.ads));
|
|
1210
1255
|
const buf = Buffer.from(modified, "utf-8");
|
|
1211
1256
|
res.setHeader("Content-Length", buf.length);
|
|
1212
1257
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
1213
|
-
for (const
|
|
1258
|
+
for (const adItem of pageAds.ads) {
|
|
1214
1259
|
client.logImpression({
|
|
1215
|
-
impression_id:
|
|
1260
|
+
impression_id: adItem.impression_id,
|
|
1216
1261
|
site_id: config.siteId,
|
|
1217
1262
|
page_path: path,
|
|
1218
1263
|
agent_ua: userAgent,
|
|
@@ -1231,7 +1276,7 @@ function createExpressMiddleware(config) {
|
|
|
1231
1276
|
};
|
|
1232
1277
|
}
|
|
1233
1278
|
res.on("finish", () => {
|
|
1234
|
-
if (shouldSkip(path)) return;
|
|
1279
|
+
if (shouldSkip(path) || isInternalRequest(req)) return;
|
|
1235
1280
|
const entry = {
|
|
1236
1281
|
site_id: config.siteId,
|
|
1237
1282
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -1242,17 +1287,21 @@ function createExpressMiddleware(config) {
|
|
|
1242
1287
|
ip_address: ipAddress,
|
|
1243
1288
|
user_agent: userAgent,
|
|
1244
1289
|
referrer: req.headers["referer"] ?? null,
|
|
1245
|
-
is_ai_crawler:
|
|
1290
|
+
is_ai_crawler: isAi,
|
|
1246
1291
|
crawler_type: crawlerInfo.name,
|
|
1247
1292
|
crawler_organization: crawlerInfo.organization,
|
|
1248
1293
|
confidence_score: crawlerInfo.confidence,
|
|
1249
|
-
scraper_service: scraperService.name
|
|
1294
|
+
scraper_service: scraperService.name,
|
|
1295
|
+
attribution_id: attributionId
|
|
1250
1296
|
};
|
|
1251
1297
|
logger.enqueue(entry);
|
|
1252
1298
|
});
|
|
1253
1299
|
next();
|
|
1254
1300
|
};
|
|
1255
1301
|
}
|
|
1302
|
+
function isInternalRequest(req) {
|
|
1303
|
+
return req.headers["x-apptvty-internal"] === "true";
|
|
1304
|
+
}
|
|
1256
1305
|
function createExpressQueryHandler(config) {
|
|
1257
1306
|
const { client } = getInstance(config);
|
|
1258
1307
|
const handleQuery = createQueryHandler(client, config);
|