apptvty 0.2.0 → 0.2.2
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-OTPVLSG5.mjs → chunk-GMQN6656.mjs} +15 -3
- package/dist/{chunk-OTPVLSG5.mjs.map → chunk-GMQN6656.mjs.map} +1 -1
- package/dist/{chunk-454YBHM2.mjs → chunk-JNM4IJDR.mjs} +4 -3
- package/dist/{chunk-454YBHM2.mjs.map → chunk-JNM4IJDR.mjs.map} +1 -1
- package/dist/{chunk-2KXDQCUZ.mjs → chunk-OZT7PIDN.mjs} +4 -3
- package/dist/{chunk-2KXDQCUZ.mjs.map → chunk-OZT7PIDN.mjs.map} +1 -1
- package/dist/cli.js +56 -6
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- 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 +16 -3
- 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 +16 -3
- package/dist/middleware/nextjs.js.map +1 -1
- package/dist/middleware/nextjs.mjs +2 -2
- package/dist/setup.d.mts +3 -0
- package/dist/setup.d.ts +3 -0
- package/dist/setup.js +4 -3
- package/dist/setup.js.map +1 -1
- package/dist/setup.mjs +4 -3
- package/dist/setup.mjs.map +1 -1
- package/dist/{types-D2A_0sPm.d.mts → types-Zt2qHOrW.d.mts} +6 -0
- package/dist/{types-D2A_0sPm.d.ts → types-Zt2qHOrW.d.ts} +6 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/middleware/express.ts"],"sourcesContent":["/**\n * Express / Node.js integration for the Apptvty SDK.\n *\n * Usage:\n *\n * import express from 'express';\n * import { createExpressMiddleware, createExpressQueryHandler } from 'apptvty/express';\n *\n * const app = express();\n * const config = { apiKey: 'ak_...', siteId: 'site_...' };\n *\n * // 1. Log all traffic + inject ads into AI crawler responses\n * app.use(createExpressMiddleware(config));\n *\n * // 2. Mount the AEO query page\n * app.get('/query', createExpressQueryHandler(config));\n *\n * Works with any Connect-compatible framework (Express, Fastify via @fastify/express, etc.)\n *\n * Ad injection strategy:\n * When an AI crawler or scraper service is detected, the middleware buffers the\n * response body and injects sponsored content using the same three-layer strategy\n * as the Next.js integration. An ad fetch runs in parallel with the app's own\n * request processing, so the latency overhead is the difference between the ad\n * fetch time and the app's own response time — typically near zero.\n */\n\nimport type { IncomingMessage, ServerResponse } from 'node:http';\nimport { ApptvtyClient } from '../client.js';\nimport { detectCrawler, detectScraperService } from '../crawler.js';\nimport { RequestLogger, getClientIp } from '../logger.js';\nimport { createQueryHandler } from '../query-handler.js';\nimport { createDashboardHandler } from '../dashboard-handler.js';\nimport { injectIntoHtml, buildSponsoredHeader, AD_INJECTION_MARKER } from '../ad-injection.js';\nimport type { ApptvtyConfig, PageAdsResponse, RequestLogEntry } from '../types.js';\n\nexport type ConnectMiddleware = (\n req: IncomingMessage,\n res: ServerResponse,\n next: (err?: unknown) => void,\n) => void;\n\nexport type ConnectHandler = (\n req: IncomingMessage,\n res: ServerResponse,\n) => void | Promise<void>;\n\n// ─── Shared singleton instances per config ────────────────────────────────────\n\nconst instances = new Map<string, { client: ApptvtyClient; logger: RequestLogger }>();\n\nfunction getInstance(config: ApptvtyConfig) {\n const key = config.apiKey;\n if (!instances.has(key)) {\n const client = new ApptvtyClient(config);\n const logger = new RequestLogger(client, config);\n instances.set(key, { client, logger });\n }\n return instances.get(key)!;\n}\n\n// ─── Traffic logging + ad injection middleware ────────────────────────────────\n\n/**\n * Express middleware that:\n * 1. Logs every request to Apptvty (all traffic, batched).\n * 2. For AI crawler and scraper service requests on HTML pages: buffers the\n * response, fetches matching ads in parallel, and injects sponsored content\n * using the three-layer strategy (content-stream, JSON-LD, response header).\n *\n * Mount this before your routes so all traffic is captured.\n *\n * @example\n * app.use(createExpressMiddleware({ apiKey: 'ak_...', siteId: 'site_...' }));\n */\nexport function createExpressMiddleware(config: ApptvtyConfig): ConnectMiddleware {\n const { client, logger } = getInstance(config);\n\n return function apptvtyMiddleware(req, res, next) {\n const startMs = Date.now();\n const userAgent = req.headers['user-agent'] ?? '';\n const crawlerInfo = detectCrawler(userAgent);\n const scraperService = detectScraperService(userAgent);\n const path = req.url ?? '/';\n const isCrawler = crawlerInfo.isAi || scraperService.isScraperService;\n const ipAddress = getClientIp(req.headers as Record<string, string | string[] | undefined>);\n\n // Start ad fetch in parallel with the app's request processing.\n // By the time res.end() is called, the fetch is usually already resolved.\n const adsPromise: Promise<PageAdsResponse> =\n isCrawler && !shouldSkip(path)\n ? client\n .getAdsForPage({ site_id: config.siteId, page_path: path })\n .catch(() => ({ ads: [] }))\n : Promise.resolve({ ads: [] });\n\n // Buffer the response body for AI/scraper traffic so we can inject ads.\n if (isCrawler && !shouldSkip(path)) {\n const chunks: Buffer[] = [];\n const originalWrite = res.write.bind(res) as typeof res.write;\n const originalEnd = res.end.bind(res) as typeof res.end;\n\n // Collect response body chunks without sending them yet.\n (res as any).write = function (\n chunk: string | Buffer | Uint8Array,\n encodingOrCallback?: BufferEncoding | ((err?: Error | null) => void),\n callback?: (err?: Error | null) => void,\n ): boolean {\n if (chunk != null) {\n chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk as string));\n }\n // Signal to the caller that the write was accepted\n if (typeof encodingOrCallback === 'function') encodingOrCallback();\n else if (typeof callback === 'function') callback();\n return true;\n };\n\n (res as any).end = function (\n chunk?: string | Buffer | Uint8Array,\n encodingOrCallback?: BufferEncoding | ((err?: Error | null) => void),\n callback?: (err?: Error | null) => void,\n ): ServerResponse {\n if (chunk != null) {\n chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk as string));\n }\n\n const contentType = (res.getHeader('content-type') as string) ?? '';\n const isHtml = contentType.includes('text/html');\n\n if (!isHtml || chunks.length === 0) {\n // Not HTML — restore and send as-is\n res.write = originalWrite;\n res.end = originalEnd;\n return originalEnd(Buffer.concat(chunks), encodingOrCallback as BufferEncoding, callback);\n }\n\n const html = Buffer.concat(chunks).toString('utf-8');\n\n if (html.includes(AD_INJECTION_MARKER)) {\n // Already injected (e.g. double middleware mount) — send as-is\n res.write = originalWrite;\n res.end = originalEnd;\n return originalEnd(html, encodingOrCallback as BufferEncoding, callback);\n }\n\n // Await the parallel ad fetch and inject, then send.\n // adsPromise is fire-and-forget from Node's perspective; we must eventually\n // call originalEnd regardless of outcome.\n adsPromise\n .then((pageAds) => {\n res.write = originalWrite;\n res.end = originalEnd;\n\n if (!pageAds.ads || pageAds.ads.length === 0) {\n originalEnd(html, encodingOrCallback as BufferEncoding, callback);\n return;\n }\n\n const modified = injectIntoHtml(html, pageAds.ads, scraperService.isScraperService);\n\n // Layer 3: response header\n res.setHeader('X-Sponsored-Content', buildSponsoredHeader(pageAds.ads));\n\n const buf = Buffer.from(modified, 'utf-8');\n // Update Content-Length now that the body has grown\n res.setHeader('Content-Length', buf.length);\n\n // Log impressions fire-and-forget — triggers billing\n const timestamp = new Date().toISOString();\n for (const ad of pageAds.ads) {\n client\n .logImpression({\n impression_id: ad.impression_id,\n site_id: config.siteId,\n page_path: path,\n agent_ua: userAgent,\n agent_ip: ipAddress,\n timestamp,\n })\n .catch(() => {});\n }\n\n originalEnd(buf, encodingOrCallback as BufferEncoding, callback);\n })\n .catch(() => {\n // Ad fetch or injection failed — send the original unmodified HTML\n res.write = originalWrite;\n res.end = originalEnd;\n originalEnd(html, encodingOrCallback as BufferEncoding, callback);\n });\n\n // Return res synchronously — Node's HTTP server will keep the connection\n // open until originalEnd is called by the promise above.\n return res;\n };\n }\n\n // Log after response completes (captures final status code and timing)\n res.on('finish', () => {\n if (shouldSkip(path)) return;\n\n const entry: RequestLogEntry = {\n site_id: config.siteId,\n timestamp: new Date().toISOString(),\n method: req.method ?? 'GET',\n path,\n status_code: res.statusCode,\n response_time_ms: Date.now() - startMs,\n ip_address: ipAddress,\n user_agent: userAgent,\n referer: (req.headers['referer'] as string) ?? null,\n is_ai_crawler: crawlerInfo.isAi,\n crawler_type: crawlerInfo.name,\n crawler_organization: crawlerInfo.organization,\n confidence_score: crawlerInfo.confidence,\n scraper_service: scraperService.name,\n };\n\n logger.enqueue(entry);\n });\n\n next();\n };\n}\n\n// ─── Query endpoint handler ───────────────────────────────────────────────────\n\n/**\n * Express route handler for the AEO query endpoint.\n *\n * Mount this at the path configured in your dashboard (default: /query).\n *\n * @example\n * app.get('/query', createExpressQueryHandler({ apiKey: 'ak_...', siteId: 'site_...' }));\n */\nexport function createExpressQueryHandler(config: ApptvtyConfig): ConnectHandler {\n const { client } = getInstance(config);\n const handleQuery = createQueryHandler(client, config);\n\n return async function queryHandler(req, res) {\n const url = new URL(req.url ?? '/', `http://${req.headers.host ?? 'localhost'}`);\n const q = url.searchParams.get('q');\n const lang = url.searchParams.get('lang');\n const surfaceAds = parseBoolParam(url.searchParams.get('surface_ads'), true);\n const aiCrawler = parseBoolParam(url.searchParams.get('ai_crawler'), false);\n const userAgent = req.headers['user-agent'] ?? '';\n\n const result = await handleQuery({\n query: q,\n lang,\n surface_ads: surfaceAds,\n ai_crawler: aiCrawler,\n userAgent,\n ipAddress: getClientIp(req.headers as Record<string, string | string[] | undefined>),\n requestUrl: url.toString(),\n });\n\n for (const [key, value] of Object.entries(result.headers)) {\n res.setHeader(key, value);\n }\n res.statusCode = result.status;\n res.end(JSON.stringify(result.body));\n };\n}\n\n/**\n * Express route handler for the embedded analytics dashboard.\n *\n * Mount this at the path where you want to view your logs (default: /apptvty/logs).\n *\n * @example\n * app.get('/apptvty/logs', createExpressDashboardHandler({ apiKey: 'ak_...', siteId: 'site_...' }));\n */\nexport function createExpressDashboardHandler(config: ApptvtyConfig): ConnectHandler {\n const { client } = getInstance(config);\n const handleDashboard = createDashboardHandler(client, config);\n\n return async function dashboardHandler(req, res) {\n const url = new URL(req.url ?? '/', `http://${req.headers.host ?? 'localhost'}`);\n const result = await handleDashboard({\n path: url.pathname + url.search,\n method: req.method ?? 'GET',\n apiKey: config.apiKey,\n siteId: config.siteId,\n });\n\n for (const [key, value] of Object.entries(result.headers)) {\n res.setHeader(key, value);\n }\n res.statusCode = result.status;\n res.end(result.body);\n };\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\nfunction parseBoolParam(value: string | null, defaultValue: boolean): boolean {\n if (value === null) return defaultValue;\n return value === '1' || value === 'true' || value === 'yes';\n}\n\nfunction shouldSkip(path: string): boolean {\n return (\n path.startsWith('/_next/') ||\n /\\.(svg|png|jpg|jpeg|gif|webp|ico|woff2?|ttf|css|js\\.map)$/.test(path)\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/middleware/express.ts"],"sourcesContent":["/**\n * Express / Node.js integration for the Apptvty SDK.\n *\n * Usage:\n *\n * import express from 'express';\n * import { createExpressMiddleware, createExpressQueryHandler } from 'apptvty/express';\n *\n * const app = express();\n * const config = { apiKey: 'ak_...', siteId: 'site_...' };\n *\n * // 1. Log all traffic + inject ads into AI crawler responses\n * app.use(createExpressMiddleware(config));\n *\n * // 2. Mount the AEO query page\n * app.get('/query', createExpressQueryHandler(config));\n *\n * Works with any Connect-compatible framework (Express, Fastify via @fastify/express, etc.)\n *\n * Ad injection strategy:\n * When an AI crawler or scraper service is detected, the middleware buffers the\n * response body and injects sponsored content using the same three-layer strategy\n * as the Next.js integration. An ad fetch runs in parallel with the app's own\n * request processing, so the latency overhead is the difference between the ad\n * fetch time and the app's own response time — typically near zero.\n */\n\nimport type { IncomingMessage, ServerResponse } from 'node:http';\nimport { ApptvtyClient } from '../client.js';\nimport { detectCrawler, detectScraperService } from '../crawler.js';\nimport { RequestLogger, getClientIp } from '../logger.js';\nimport { createQueryHandler } from '../query-handler.js';\nimport { createDashboardHandler } from '../dashboard-handler.js';\nimport { injectIntoHtml, buildSponsoredHeader, AD_INJECTION_MARKER } from '../ad-injection.js';\nimport type { ApptvtyConfig, PageAdsResponse, RequestLogEntry } from '../types.js';\n\nexport type ConnectMiddleware = (\n req: IncomingMessage,\n res: ServerResponse,\n next: (err?: unknown) => void,\n) => void;\n\nexport type ConnectHandler = (\n req: IncomingMessage,\n res: ServerResponse,\n) => void | Promise<void>;\n\n// ─── Shared singleton instances per config ────────────────────────────────────\n\nconst instances = new Map<string, { client: ApptvtyClient; logger: RequestLogger }>();\n\nfunction getInstance(config: ApptvtyConfig) {\n const key = config.apiKey;\n if (!instances.has(key)) {\n const client = new ApptvtyClient(config);\n const logger = new RequestLogger(client, config);\n instances.set(key, { client, logger });\n }\n return instances.get(key)!;\n}\n\n// ─── Traffic logging + ad injection middleware ────────────────────────────────\n\n/**\n * Express middleware that:\n * 1. Logs every request to Apptvty (all traffic, batched).\n * 2. For AI crawler and scraper service requests on HTML pages: buffers the\n * response, fetches matching ads in parallel, and injects sponsored content\n * using the three-layer strategy (content-stream, JSON-LD, response header).\n *\n * Mount this before your routes so all traffic is captured.\n *\n * @example\n * app.use(createExpressMiddleware({ apiKey: 'ak_...', siteId: 'site_...' }));\n */\nexport function createExpressMiddleware(config: ApptvtyConfig): ConnectMiddleware {\n const { client, logger } = getInstance(config);\n\n return function apptvtyMiddleware(req, res, next) {\n const startMs = Date.now();\n const userAgent = req.headers['user-agent'] ?? '';\n const crawlerInfo = detectCrawler(userAgent);\n const scraperService = detectScraperService(userAgent);\n const path = req.url ?? '/';\n const isCrawler = crawlerInfo.isAi || scraperService.isScraperService;\n const ipAddress = getClientIp(req.headers as Record<string, string | string[] | undefined>);\n\n // Start ad fetch in parallel with the app's request processing.\n // By the time res.end() is called, the fetch is usually already resolved.\n const adsPromise: Promise<PageAdsResponse> =\n isCrawler && !shouldSkip(path)\n ? client\n .getAdsForPage({ site_id: config.siteId, page_path: path })\n .catch(() => ({ ads: [] }))\n : Promise.resolve({ ads: [] });\n\n // Buffer the response body for AI/scraper traffic so we can inject ads.\n if (isCrawler && !shouldSkip(path)) {\n const chunks: Buffer[] = [];\n const originalWrite = res.write.bind(res) as typeof res.write;\n const originalEnd = res.end.bind(res) as typeof res.end;\n\n // Collect response body chunks without sending them yet.\n (res as any).write = function (\n chunk: string | Buffer | Uint8Array,\n encodingOrCallback?: BufferEncoding | ((err?: Error | null) => void),\n callback?: (err?: Error | null) => void,\n ): boolean {\n if (chunk != null) {\n chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk as string));\n }\n // Signal to the caller that the write was accepted\n if (typeof encodingOrCallback === 'function') encodingOrCallback();\n else if (typeof callback === 'function') callback();\n return true;\n };\n\n (res as any).end = function (\n chunk?: string | Buffer | Uint8Array,\n encodingOrCallback?: BufferEncoding | ((err?: Error | null) => void),\n callback?: (err?: Error | null) => void,\n ): ServerResponse {\n if (chunk != null) {\n chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk as string));\n }\n\n const contentType = (res.getHeader('content-type') as string) ?? '';\n const isHtml = contentType.includes('text/html');\n\n if (!isHtml || chunks.length === 0) {\n // Not HTML — restore and send as-is\n res.write = originalWrite;\n res.end = originalEnd;\n return originalEnd(Buffer.concat(chunks), encodingOrCallback as BufferEncoding, callback);\n }\n\n const html = Buffer.concat(chunks).toString('utf-8');\n\n if (html.includes(AD_INJECTION_MARKER)) {\n // Already injected (e.g. double middleware mount) — send as-is\n res.write = originalWrite;\n res.end = originalEnd;\n return originalEnd(html, encodingOrCallback as BufferEncoding, callback);\n }\n\n // Await the parallel ad fetch and inject, then send.\n // adsPromise is fire-and-forget from Node's perspective; we must eventually\n // call originalEnd regardless of outcome.\n adsPromise\n .then((pageAds) => {\n res.write = originalWrite;\n res.end = originalEnd;\n\n if (!pageAds.ads || pageAds.ads.length === 0) {\n originalEnd(html, encodingOrCallback as BufferEncoding, callback);\n return;\n }\n\n const modified = injectIntoHtml(html, pageAds.ads, scraperService.isScraperService);\n\n // Layer 3: response header\n res.setHeader('X-Sponsored-Content', buildSponsoredHeader(pageAds.ads));\n\n const buf = Buffer.from(modified, 'utf-8');\n // Update Content-Length now that the body has grown\n res.setHeader('Content-Length', buf.length);\n\n // Log impressions fire-and-forget — triggers billing\n const timestamp = new Date().toISOString();\n for (const ad of pageAds.ads) {\n client\n .logImpression({\n impression_id: ad.impression_id,\n site_id: config.siteId,\n page_path: path,\n agent_ua: userAgent,\n agent_ip: ipAddress,\n timestamp,\n })\n .catch(() => {});\n }\n\n originalEnd(buf, encodingOrCallback as BufferEncoding, callback);\n })\n .catch(() => {\n // Ad fetch or injection failed — send the original unmodified HTML\n res.write = originalWrite;\n res.end = originalEnd;\n originalEnd(html, encodingOrCallback as BufferEncoding, callback);\n });\n\n // Return res synchronously — Node's HTTP server will keep the connection\n // open until originalEnd is called by the promise above.\n return res;\n };\n }\n\n // Log after response completes (captures final status code and timing)\n res.on('finish', () => {\n if (shouldSkip(path)) return;\n\n const entry: RequestLogEntry = {\n site_id: config.siteId,\n timestamp: new Date().toISOString(),\n method: req.method ?? 'GET',\n path,\n status_code: res.statusCode,\n response_time_ms: Date.now() - startMs,\n ip_address: ipAddress,\n user_agent: userAgent,\n referer: (req.headers['referer'] as string) ?? null,\n is_ai_crawler: crawlerInfo.isAi,\n crawler_type: crawlerInfo.name,\n crawler_organization: crawlerInfo.organization,\n confidence_score: crawlerInfo.confidence,\n scraper_service: scraperService.name,\n };\n\n logger.enqueue(entry);\n });\n\n next();\n };\n}\n\n// ─── Query endpoint handler ───────────────────────────────────────────────────\n\n/**\n * Express route handler for the AEO query endpoint.\n *\n * Mount this at the path configured in your dashboard (default: /query).\n *\n * @example\n * app.get('/query', createExpressQueryHandler({ apiKey: 'ak_...', siteId: 'site_...' }));\n */\nexport function createExpressQueryHandler(config: ApptvtyConfig): ConnectHandler {\n const { client } = getInstance(config);\n const handleQuery = createQueryHandler(client, config);\n\n return async function queryHandler(req, res) {\n const url = new URL(req.url ?? '/', `http://${req.headers.host ?? 'localhost'}`);\n const q = url.searchParams.get('q');\n const lang = url.searchParams.get('lang');\n const surfaceAds = parseBoolParam(url.searchParams.get('surface_ads'), true);\n const aiCrawler = parseBoolParam(url.searchParams.get('ai_crawler'), false);\n const userAgent = req.headers['user-agent'] ?? '';\n\n const result = await handleQuery({\n query: q,\n lang,\n surface_ads: surfaceAds,\n ai_crawler: aiCrawler,\n userAgent,\n ipAddress: getClientIp(req.headers as Record<string, string | string[] | undefined>),\n requestUrl: url.toString(),\n });\n\n for (const [key, value] of Object.entries(result.headers)) {\n res.setHeader(key, value);\n }\n res.statusCode = result.status;\n res.end(JSON.stringify(result.body));\n };\n}\n\n/**\n * Express route handler for the embedded analytics dashboard.\n *\n * Mount this at the path where you want to view your logs (default: /apptvty/logs).\n *\n * @example\n * app.get('/apptvty/logs', createExpressDashboardHandler({ apiKey: 'ak_...', siteId: 'site_...' }));\n */\nexport function createExpressDashboardHandler(config: ApptvtyConfig): ConnectHandler {\n const { client } = getInstance(config);\n const handleDashboard = createDashboardHandler(client, config);\n\n return async function dashboardHandler(req, res) {\n const url = new URL(req.url ?? '/', `http://${req.headers.host ?? 'localhost'}`);\n const result = await handleDashboard({\n path: url.pathname + url.search,\n method: req.method ?? 'GET',\n apiKey: config.apiKey,\n siteId: config.siteId,\n authHeader: (req.headers['authorization'] as string) ?? null,\n });\n\n for (const [key, value] of Object.entries(result.headers)) {\n res.setHeader(key, value);\n }\n res.statusCode = result.status;\n res.end(result.body);\n };\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\nfunction parseBoolParam(value: string | null, defaultValue: boolean): boolean {\n if (value === null) return defaultValue;\n return value === '1' || value === 'true' || value === 'yes';\n}\n\nfunction shouldSkip(path: string): boolean {\n return (\n path.startsWith('/_next/') ||\n /\\.(svg|png|jpg|jpeg|gif|webp|ico|woff2?|ttf|css|js\\.map)$/.test(path)\n );\n}\n"],"mappings":"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","names":[]}
|
package/dist/cli.js
CHANGED
|
@@ -46,12 +46,13 @@ async function register(options) {
|
|
|
46
46
|
trialEndsAt: data.trial_ends_at,
|
|
47
47
|
email: data.email ?? null,
|
|
48
48
|
setup: {
|
|
49
|
-
|
|
49
|
+
envFile: data.env_file || (data.framework === "nextjs" ? ".env.local" : ".env"),
|
|
50
|
+
envVars: data.env_vars || {
|
|
50
51
|
APPTVTY_SITE_ID: data.site_id,
|
|
51
52
|
APPTVTY_API_KEY: data.api_key
|
|
52
53
|
},
|
|
53
|
-
files: data.
|
|
54
|
-
x402: data.
|
|
54
|
+
files: data.files,
|
|
55
|
+
x402: data.x402
|
|
55
56
|
}
|
|
56
57
|
};
|
|
57
58
|
}
|
|
@@ -89,7 +90,8 @@ async function migrate(options) {
|
|
|
89
90
|
var args = process.argv.slice(2);
|
|
90
91
|
var rawCmd = args[0];
|
|
91
92
|
var isMigrate = rawCmd === "migrate";
|
|
92
|
-
var
|
|
93
|
+
var isCreateSuperuser = rawCmd === "create-superuser";
|
|
94
|
+
var cmdArgs = isMigrate || isCreateSuperuser ? args.slice(1) : args;
|
|
93
95
|
function getFlag(name, from = cmdArgs) {
|
|
94
96
|
const idx = from.indexOf(`--${name}`);
|
|
95
97
|
return idx !== -1 ? from[idx + 1] : void 0;
|
|
@@ -191,7 +193,11 @@ function loadEnvVars() {
|
|
|
191
193
|
const m = line.match(/^\s*([A-Z_][A-Z0-9_]*)\s*=\s*(.+?)\s*$/);
|
|
192
194
|
if (m) out[m[1]] = m[2].replace(/^["']|["']$/g, "").trim();
|
|
193
195
|
}
|
|
194
|
-
return {
|
|
196
|
+
return {
|
|
197
|
+
siteId: out.APPTVTY_SITE_ID,
|
|
198
|
+
apiKey: out.APPTVTY_API_KEY,
|
|
199
|
+
claimToken: out.APPTVTY_CLAIM_TOKEN
|
|
200
|
+
};
|
|
195
201
|
}
|
|
196
202
|
async function runInit() {
|
|
197
203
|
const framework = flagFramework ?? detectFramework();
|
|
@@ -245,7 +251,7 @@ async function runInit() {
|
|
|
245
251
|
if (!isNonInteractive) {
|
|
246
252
|
console.log(" done\n");
|
|
247
253
|
}
|
|
248
|
-
const envFile = framework === "nextjs" ? ".env.local" : ".env";
|
|
254
|
+
const envFile = result.setup.envFile || (framework === "nextjs" ? ".env.local" : ".env");
|
|
249
255
|
appendEnvFile(envFile, result.setup.envVars);
|
|
250
256
|
const scaffolded = [];
|
|
251
257
|
if (result.setup.files && !isNonInteractive) {
|
|
@@ -325,6 +331,48 @@ async function runInit() {
|
|
|
325
331
|
console.log(" GET /v1/wallet/sync?tx_hash=<your_transaction_hash>");
|
|
326
332
|
console.log(" Log in once to keep your credentials and continue receiving analytics.\n");
|
|
327
333
|
}
|
|
334
|
+
async function runCreateSuperuser() {
|
|
335
|
+
console.log("\n \u{1F680} Apptvty \u2014 Create Superuser\n");
|
|
336
|
+
const { claimToken } = loadEnvVars();
|
|
337
|
+
if (!claimToken) {
|
|
338
|
+
console.error("\n \u2716 Error: APPTVTY_CLAIM_TOKEN not found in .env.local or .env");
|
|
339
|
+
console.log(" Please run `npx apptvty init` first to initialize your site.\n");
|
|
340
|
+
process.exit(1);
|
|
341
|
+
}
|
|
342
|
+
const email = await prompt(" Email: ");
|
|
343
|
+
const password = await prompt(" Password: ");
|
|
344
|
+
const name = await prompt(" Full Name (optional): ");
|
|
345
|
+
const apiUrl = flagApiUrl || "https://api.apptvty.com";
|
|
346
|
+
try {
|
|
347
|
+
const response = await fetch(`${apiUrl}/v1/auth/claim`, {
|
|
348
|
+
method: "POST",
|
|
349
|
+
headers: {
|
|
350
|
+
"Content-Type": "application/json"
|
|
351
|
+
},
|
|
352
|
+
body: JSON.stringify({
|
|
353
|
+
claim_token: claimToken,
|
|
354
|
+
email,
|
|
355
|
+
password,
|
|
356
|
+
name
|
|
357
|
+
})
|
|
358
|
+
});
|
|
359
|
+
const result = await response.json();
|
|
360
|
+
if (!response.ok) {
|
|
361
|
+
console.error(`
|
|
362
|
+
\u2716 Error: ${result.message || result.error?.message || response.statusText}`);
|
|
363
|
+
process.exit(1);
|
|
364
|
+
}
|
|
365
|
+
console.log("\n \u2713 Superuser created successfully!");
|
|
366
|
+
console.log(` \u2713 Email: ${result.user.email}`);
|
|
367
|
+
console.log(` \u2713 Company: ${result.company.name}
|
|
368
|
+
`);
|
|
369
|
+
console.log(" You can now log in at https://dashboard.apptvty.com\n");
|
|
370
|
+
} catch (err) {
|
|
371
|
+
console.error(`
|
|
372
|
+
\u2716 Network error: ${err.message}`);
|
|
373
|
+
process.exit(1);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
328
376
|
async function runMigrate() {
|
|
329
377
|
const { siteId, apiKey } = loadEnvVars();
|
|
330
378
|
if (!siteId || !apiKey) {
|
|
@@ -368,6 +416,8 @@ async function runMigrate() {
|
|
|
368
416
|
async function main() {
|
|
369
417
|
if (isMigrate) {
|
|
370
418
|
await runMigrate();
|
|
419
|
+
} else if (isCreateSuperuser) {
|
|
420
|
+
await runCreateSuperuser();
|
|
371
421
|
} else {
|
|
372
422
|
await runInit();
|
|
373
423
|
}
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { C as CrawlerInfo, A as ApptvtyConfig, R as RequestLogEntry, Q as QueryRequest, B as BackendQueryResponse, P as PageAdsResponse, I as ImpressionLog, S as SiteOverviewStats, D as DailyStat, a as RecentActivityItem, b as RecentQueryItem, c as CrawlerBreakdown, d as SiteWalletInfo, e as CreateCampaignParams, f as CampaignRecord, U as UpdateCampaignParams, g as InsufficientBalanceError, h as AgentQueryResponse, i as QueryEndpointDiscovery, j as AgentErrorResponse } from './types-
|
|
2
|
-
export { k as CampaignStatus, l as PageAd, m as QuerySource, n as SponsoredAd } from './types-
|
|
1
|
+
import { C as CrawlerInfo, A as ApptvtyConfig, R as RequestLogEntry, Q as QueryRequest, B as BackendQueryResponse, P as PageAdsResponse, I as ImpressionLog, S as SiteOverviewStats, D as DailyStat, a as RecentActivityItem, b as RecentQueryItem, c as CrawlerBreakdown, d as SiteWalletInfo, e as CreateCampaignParams, f as CampaignRecord, U as UpdateCampaignParams, g as InsufficientBalanceError, h as AgentQueryResponse, i as QueryEndpointDiscovery, j as AgentErrorResponse } from './types-Zt2qHOrW.mjs';
|
|
2
|
+
export { k as CampaignStatus, l as PageAd, m as QuerySource, n as SponsoredAd } from './types-Zt2qHOrW.mjs';
|
|
3
3
|
export { createNextjsQueryHandler, withApptvty } from './middleware/nextjs.mjs';
|
|
4
4
|
export { createExpressMiddleware, createExpressQueryHandler } from './middleware/express.mjs';
|
|
5
5
|
import 'next/server';
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { C as CrawlerInfo, A as ApptvtyConfig, R as RequestLogEntry, Q as QueryRequest, B as BackendQueryResponse, P as PageAdsResponse, I as ImpressionLog, S as SiteOverviewStats, D as DailyStat, a as RecentActivityItem, b as RecentQueryItem, c as CrawlerBreakdown, d as SiteWalletInfo, e as CreateCampaignParams, f as CampaignRecord, U as UpdateCampaignParams, g as InsufficientBalanceError, h as AgentQueryResponse, i as QueryEndpointDiscovery, j as AgentErrorResponse } from './types-
|
|
2
|
-
export { k as CampaignStatus, l as PageAd, m as QuerySource, n as SponsoredAd } from './types-
|
|
1
|
+
import { C as CrawlerInfo, A as ApptvtyConfig, R as RequestLogEntry, Q as QueryRequest, B as BackendQueryResponse, P as PageAdsResponse, I as ImpressionLog, S as SiteOverviewStats, D as DailyStat, a as RecentActivityItem, b as RecentQueryItem, c as CrawlerBreakdown, d as SiteWalletInfo, e as CreateCampaignParams, f as CampaignRecord, U as UpdateCampaignParams, g as InsufficientBalanceError, h as AgentQueryResponse, i as QueryEndpointDiscovery, j as AgentErrorResponse } from './types-Zt2qHOrW.js';
|
|
2
|
+
export { k as CampaignStatus, l as PageAd, m as QuerySource, n as SponsoredAd } from './types-Zt2qHOrW.js';
|
|
3
3
|
export { createNextjsQueryHandler, withApptvty } from './middleware/nextjs.js';
|
|
4
4
|
export { createExpressMiddleware, createExpressQueryHandler } from './middleware/express.js';
|
|
5
5
|
import 'next/server';
|