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
package/dist/index.js
CHANGED
|
@@ -127,6 +127,28 @@ var ApptvtyClient = class {
|
|
|
127
127
|
this.warn("Failed to log impression (billing may be delayed):", err);
|
|
128
128
|
}
|
|
129
129
|
}
|
|
130
|
+
// ─── Programmatic Indexing ───────────────────────────────────────────────────
|
|
131
|
+
/**
|
|
132
|
+
* Manually push text or markdown directly into your site's RAG vector database.
|
|
133
|
+
* This instantly embeds the content for AI agents to query, bypassing the
|
|
134
|
+
* need for a scheduled HTML crawl. This is ideal for Client-Side Rendered (CSR)
|
|
135
|
+
* Apps or for injecting private institutional facts/databases.
|
|
136
|
+
*
|
|
137
|
+
* @example
|
|
138
|
+
* const result = await client.pushKnowledge({
|
|
139
|
+
* title: "Capitol Technology University Profile",
|
|
140
|
+
* content: "Here are all the detailed specs, majors, and acceptances...",
|
|
141
|
+
* url: "https://thecollegeradar.com/institution/499" // Optional reference
|
|
142
|
+
* });
|
|
143
|
+
*/
|
|
144
|
+
async pushKnowledge(params) {
|
|
145
|
+
const payload = {
|
|
146
|
+
title: params.title,
|
|
147
|
+
content: params.content,
|
|
148
|
+
url: params.url
|
|
149
|
+
};
|
|
150
|
+
return this.post(`/v1/sites/${this.siteId}/knowledge`, payload);
|
|
151
|
+
}
|
|
130
152
|
// ─── Analytics (for coding agents) ───────────────────────────────────────────
|
|
131
153
|
// These allow agents to check activity, logs, and errors without a human.
|
|
132
154
|
/** Get 30-day traffic overview (requests, AI %, crawlers, queries). */
|
|
@@ -374,8 +396,12 @@ var ApptvtyClient = class {
|
|
|
374
396
|
let dashboardUrl = (typeof process !== "undefined" ? process.env?.DASHBOARD_URL : void 0) ?? "https://dashboard.apptvty.com/login";
|
|
375
397
|
try {
|
|
376
398
|
const json = JSON.parse(text);
|
|
399
|
+
if (json?.error?.code === "INSUFFICIENT_BALANCE") {
|
|
400
|
+
throw new ApptvtyApiError(402, path, text);
|
|
401
|
+
}
|
|
377
402
|
dashboardUrl = json?.error?.details?.dashboard_url ?? dashboardUrl;
|
|
378
|
-
} catch {
|
|
403
|
+
} catch (e) {
|
|
404
|
+
if (e instanceof ApptvtyApiError) throw e;
|
|
379
405
|
}
|
|
380
406
|
throw new ApptvtyTrialExpiredError(dashboardUrl);
|
|
381
407
|
}
|
|
@@ -861,7 +887,8 @@ function withApptvty(config, next) {
|
|
|
861
887
|
const crawlerInfo = detectCrawler(userAgent);
|
|
862
888
|
const scraperService = detectScraperService(userAgent);
|
|
863
889
|
const aiCrawlerParam = parseBoolParam(request.nextUrl.searchParams.get("ai_crawler"), false);
|
|
864
|
-
const
|
|
890
|
+
const isAi = crawlerInfo.isAi || aiCrawlerParam;
|
|
891
|
+
const isScraper = isAi || scraperService.isScraperService || crawlerInfo.name === "unknown_bot";
|
|
865
892
|
if (request.nextUrl.pathname === "/api/apptvty/verify") {
|
|
866
893
|
const challenge = request.nextUrl.searchParams.get("challenge");
|
|
867
894
|
if (challenge) {
|
|
@@ -906,59 +933,87 @@ function withApptvty(config, next) {
|
|
|
906
933
|
ip_address: getClientIp(headers),
|
|
907
934
|
user_agent: userAgent,
|
|
908
935
|
referrer: request.headers.get("referer"),
|
|
909
|
-
is_ai_crawler:
|
|
936
|
+
is_ai_crawler: isAi,
|
|
910
937
|
crawler_type: crawlerInfo.name,
|
|
911
938
|
crawler_organization: crawlerInfo.organization,
|
|
912
939
|
confidence_score: crawlerInfo.confidence,
|
|
913
|
-
scraper_service: scraperService.name
|
|
940
|
+
scraper_service: scraperService.name,
|
|
941
|
+
attribution_id: request.nextUrl.searchParams.get("atid")
|
|
914
942
|
};
|
|
915
|
-
const
|
|
916
|
-
if (!
|
|
943
|
+
const isInternalRequest2 = request.headers.get("x-apptvty-internal") === "true";
|
|
944
|
+
if (!isInternalRequest2 && !pathname.startsWith(queryPath)) {
|
|
917
945
|
logger.enqueue(entry);
|
|
918
946
|
if (event && typeof event.waitUntil === "function") {
|
|
919
947
|
event.waitUntil(logger.flush());
|
|
920
948
|
}
|
|
921
949
|
}
|
|
922
|
-
if (
|
|
950
|
+
if (!isInternalRequest2 && !pathname.startsWith(queryPath) && response.status === 200) {
|
|
923
951
|
try {
|
|
924
|
-
const
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
952
|
+
const pageAds = await client.getAdsForPage({ site_id: config.siteId, page_path: pathname });
|
|
953
|
+
if (pageAds.ads && pageAds.ads.length > 0) {
|
|
954
|
+
const ad = pageAds.ads[0];
|
|
955
|
+
client.logImpression({
|
|
956
|
+
impression_id: ad.impression_id,
|
|
957
|
+
site_id: config.siteId,
|
|
958
|
+
page_path: pathname,
|
|
959
|
+
agent_ua: userAgent,
|
|
960
|
+
agent_ip: getClientIp(headers),
|
|
961
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
962
|
+
}).catch(() => {
|
|
963
|
+
});
|
|
964
|
+
if (isAi || scraperService.isScraperService) {
|
|
965
|
+
const proxyReq = new Request(request.url, { headers: new Headers(request.headers) });
|
|
966
|
+
proxyReq.headers.set("x-apptvty-internal", "true");
|
|
967
|
+
const res = await fetch(proxyReq);
|
|
968
|
+
const contentType = res.headers.get("content-type") ?? "";
|
|
969
|
+
if (contentType.includes("text/html")) {
|
|
970
|
+
const html = await res.text();
|
|
971
|
+
let markdown = convertHtmlToMarkdown(html);
|
|
972
|
+
markdown += `
|
|
937
973
|
|
|
938
974
|
---
|
|
939
975
|
> **Sponsored:** [${ad.text}](${ad.url}) - ${ad.advertiser}
|
|
940
976
|
`;
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
}
|
|
977
|
+
return new import_server.NextResponse(markdown, {
|
|
978
|
+
status: res.status,
|
|
979
|
+
headers: {
|
|
980
|
+
"Content-Type": "text/markdown",
|
|
981
|
+
"X-Apptvty-AEO": "true",
|
|
982
|
+
"X-Sponsored-Content": `${ad.text}; url=${ad.url}`
|
|
983
|
+
}
|
|
984
|
+
});
|
|
985
|
+
}
|
|
950
986
|
}
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
987
|
+
const originalHeaders = headersToRecord(response.headers);
|
|
988
|
+
if (originalHeaders["content-type"]?.includes("text/html")) {
|
|
989
|
+
const html = await response.text();
|
|
990
|
+
const jsonLd = `
|
|
991
|
+
<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>
|
|
992
|
+
`;
|
|
993
|
+
const stealthDiv = `
|
|
994
|
+
<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>
|
|
995
|
+
`;
|
|
996
|
+
let modifiedHtml = html;
|
|
997
|
+
if (html.includes("</head>")) {
|
|
998
|
+
modifiedHtml = html.replace("</head>", `${jsonLd}</head>`);
|
|
956
999
|
}
|
|
957
|
-
|
|
1000
|
+
if (modifiedHtml.includes("</body>")) {
|
|
1001
|
+
modifiedHtml = modifiedHtml.replace("</body>", `${stealthDiv}</body>`);
|
|
1002
|
+
} else {
|
|
1003
|
+
modifiedHtml += stealthDiv;
|
|
1004
|
+
}
|
|
1005
|
+
return new import_server.NextResponse(modifiedHtml, {
|
|
1006
|
+
status: response.status,
|
|
1007
|
+
headers: {
|
|
1008
|
+
...originalHeaders,
|
|
1009
|
+
"X-Sponsored-Content": `${ad.text}; url=${ad.url}`
|
|
1010
|
+
}
|
|
1011
|
+
});
|
|
1012
|
+
}
|
|
1013
|
+
response.headers.set("X-Sponsored-Content", `${ad.text}; url=${ad.url}`);
|
|
958
1014
|
}
|
|
959
|
-
return res;
|
|
960
1015
|
} catch (err) {
|
|
961
|
-
if (config.debug) console.warn("[apptvty]
|
|
1016
|
+
if (config.debug) console.warn("[apptvty] Stealth injection failed:", err);
|
|
962
1017
|
}
|
|
963
1018
|
}
|
|
964
1019
|
return response;
|
|
@@ -1074,13 +1129,18 @@ function createExpressMiddleware(config) {
|
|
|
1074
1129
|
const crawlerInfo = detectCrawler(userAgent);
|
|
1075
1130
|
const scraperService = detectScraperService(userAgent);
|
|
1076
1131
|
const path = req.url ?? "/";
|
|
1077
|
-
const isCrawler = crawlerInfo.isAi || scraperService.isScraperService;
|
|
1078
1132
|
const ipAddress = getClientIp(req.headers);
|
|
1079
|
-
const
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1133
|
+
const urlObj = new URL(path, `http://${req.headers.host ?? "localhost"}`);
|
|
1134
|
+
const aiCrawlerParam = parseBoolParam2(urlObj.searchParams.get("ai_crawler"), false);
|
|
1135
|
+
const attributionId = urlObj.searchParams.get("atid");
|
|
1136
|
+
const isAi = crawlerInfo.isAi || aiCrawlerParam;
|
|
1137
|
+
const isScraper = isAi || scraperService.isScraperService || crawlerInfo.name === "unknown_bot";
|
|
1138
|
+
const adsPromise = !isInternalRequest(req) && !shouldSkip2(path) ? client.getAdsForPage({ site_id: config.siteId, page_path: path }).catch(() => ({ ads: [] })) : Promise.resolve({ ads: [] });
|
|
1139
|
+
const chunks = [];
|
|
1140
|
+
const originalWrite = res.write.bind(res);
|
|
1141
|
+
const originalEnd = res.end.bind(res);
|
|
1142
|
+
const shouldBuffer = !isInternalRequest(req) && !shouldSkip2(path);
|
|
1143
|
+
if (shouldBuffer) {
|
|
1084
1144
|
res.write = function(chunk, encodingOrCallback, callback) {
|
|
1085
1145
|
if (chunk != null) {
|
|
1086
1146
|
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
@@ -1095,17 +1155,12 @@ function createExpressMiddleware(config) {
|
|
|
1095
1155
|
}
|
|
1096
1156
|
const contentType = res.getHeader("content-type") ?? "";
|
|
1097
1157
|
const isHtml = contentType.includes("text/html");
|
|
1098
|
-
if (!isHtml || chunks.length === 0) {
|
|
1158
|
+
if (!isHtml || chunks.length === 0 || res.statusCode !== 200) {
|
|
1099
1159
|
res.write = originalWrite;
|
|
1100
1160
|
res.end = originalEnd;
|
|
1101
1161
|
return originalEnd(Buffer.concat(chunks), encodingOrCallback, callback);
|
|
1102
1162
|
}
|
|
1103
1163
|
const html = Buffer.concat(chunks).toString("utf-8");
|
|
1104
|
-
if (html.includes(AD_INJECTION_MARKER)) {
|
|
1105
|
-
res.write = originalWrite;
|
|
1106
|
-
res.end = originalEnd;
|
|
1107
|
-
return originalEnd(html, encodingOrCallback, callback);
|
|
1108
|
-
}
|
|
1109
1164
|
adsPromise.then((pageAds) => {
|
|
1110
1165
|
res.write = originalWrite;
|
|
1111
1166
|
res.end = originalEnd;
|
|
@@ -1113,14 +1168,33 @@ function createExpressMiddleware(config) {
|
|
|
1113
1168
|
originalEnd(html, encodingOrCallback, callback);
|
|
1114
1169
|
return;
|
|
1115
1170
|
}
|
|
1116
|
-
const
|
|
1171
|
+
const ad = pageAds.ads[0];
|
|
1172
|
+
let modified = html;
|
|
1173
|
+
if (isAi || scraperService.isScraperService) {
|
|
1174
|
+
modified = injectIntoHtml(html, pageAds.ads, scraperService.isScraperService);
|
|
1175
|
+
} else {
|
|
1176
|
+
const jsonLd = `
|
|
1177
|
+
<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>
|
|
1178
|
+
`;
|
|
1179
|
+
const stealthDiv = `
|
|
1180
|
+
<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>
|
|
1181
|
+
`;
|
|
1182
|
+
if (html.includes("</head>")) {
|
|
1183
|
+
modified = html.replace("</head>", `${jsonLd}</head>`);
|
|
1184
|
+
}
|
|
1185
|
+
if (modified.includes("</body>")) {
|
|
1186
|
+
modified = modified.replace("</body>", `${stealthDiv}</body>`);
|
|
1187
|
+
} else {
|
|
1188
|
+
modified += stealthDiv;
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1117
1191
|
res.setHeader("X-Sponsored-Content", buildSponsoredHeader(pageAds.ads));
|
|
1118
1192
|
const buf = Buffer.from(modified, "utf-8");
|
|
1119
1193
|
res.setHeader("Content-Length", buf.length);
|
|
1120
1194
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
1121
|
-
for (const
|
|
1195
|
+
for (const adItem of pageAds.ads) {
|
|
1122
1196
|
client.logImpression({
|
|
1123
|
-
impression_id:
|
|
1197
|
+
impression_id: adItem.impression_id,
|
|
1124
1198
|
site_id: config.siteId,
|
|
1125
1199
|
page_path: path,
|
|
1126
1200
|
agent_ua: userAgent,
|
|
@@ -1139,7 +1213,7 @@ function createExpressMiddleware(config) {
|
|
|
1139
1213
|
};
|
|
1140
1214
|
}
|
|
1141
1215
|
res.on("finish", () => {
|
|
1142
|
-
if (shouldSkip2(path)) return;
|
|
1216
|
+
if (shouldSkip2(path) || isInternalRequest(req)) return;
|
|
1143
1217
|
const entry = {
|
|
1144
1218
|
site_id: config.siteId,
|
|
1145
1219
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -1150,17 +1224,21 @@ function createExpressMiddleware(config) {
|
|
|
1150
1224
|
ip_address: ipAddress,
|
|
1151
1225
|
user_agent: userAgent,
|
|
1152
1226
|
referrer: req.headers["referer"] ?? null,
|
|
1153
|
-
is_ai_crawler:
|
|
1227
|
+
is_ai_crawler: isAi,
|
|
1154
1228
|
crawler_type: crawlerInfo.name,
|
|
1155
1229
|
crawler_organization: crawlerInfo.organization,
|
|
1156
1230
|
confidence_score: crawlerInfo.confidence,
|
|
1157
|
-
scraper_service: scraperService.name
|
|
1231
|
+
scraper_service: scraperService.name,
|
|
1232
|
+
attribution_id: attributionId
|
|
1158
1233
|
};
|
|
1159
1234
|
logger.enqueue(entry);
|
|
1160
1235
|
});
|
|
1161
1236
|
next();
|
|
1162
1237
|
};
|
|
1163
1238
|
}
|
|
1239
|
+
function isInternalRequest(req) {
|
|
1240
|
+
return req.headers["x-apptvty-internal"] === "true";
|
|
1241
|
+
}
|
|
1164
1242
|
function createExpressQueryHandler(config) {
|
|
1165
1243
|
const { client } = getInstance2(config);
|
|
1166
1244
|
const handleQuery = createQueryHandler(client, config);
|