apptvty 0.2.4 → 0.3.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.
@@ -352,7 +352,7 @@ var ApptvtyClient = class {
352
352
  this.log("X402 challenge auto-paid from wallet, retrying request");
353
353
  return this.post(path, body);
354
354
  }
355
- let dashboardUrl = "https://dashboard.apptvty.com/login";
355
+ let dashboardUrl = (typeof process !== "undefined" ? process.env?.DASHBOARD_URL : void 0) ?? "https://dashboard.apptvty.com/login";
356
356
  try {
357
357
  const json = JSON.parse(text);
358
358
  dashboardUrl = json?.error?.details?.dashboard_url ?? dashboardUrl;
@@ -534,7 +534,7 @@ var RequestLogger = class {
534
534
  this.client = client;
535
535
  this.queue = [];
536
536
  this.timer = null;
537
- this.flushing = false;
537
+ this.activeFlushPromise = null;
538
538
  this.batchSize = config.batchSize ?? 50;
539
539
  this.debug = config.debug ?? false;
540
540
  const interval = config.flushInterval ?? 5e3;
@@ -559,21 +559,31 @@ var RequestLogger = class {
559
559
  /** Enqueue a single log entry. Non-blocking. */
560
560
  enqueue(entry) {
561
561
  this.queue.push(entry);
562
- if (this.queue.length >= this.batchSize) {
563
- void this.flush();
564
- }
562
+ void this.flush();
565
563
  }
566
- /** Flush the current queue to the API. */
564
+ /** Flush the current queue to the API. Returns the active Promise so Edge runtimes can wait. */
567
565
  async flush() {
568
- if (this.flushing || this.queue.length === 0) return;
569
- this.flushing = true;
570
- const batch = this.queue.splice(0, this.batchSize);
571
- try {
572
- await this.client.sendLogs(batch);
573
- } catch {
574
- } finally {
575
- this.flushing = false;
566
+ if (this.queue.length === 0) {
567
+ return this.activeFlushPromise || Promise.resolve();
568
+ }
569
+ if (this.activeFlushPromise) {
570
+ await this.activeFlushPromise;
571
+ if (this.queue.length > 0) return this.flush();
572
+ return Promise.resolve();
576
573
  }
574
+ const batch = this.queue.splice(0, this.batchSize);
575
+ this.activeFlushPromise = (async () => {
576
+ try {
577
+ await this.client.sendLogs(batch);
578
+ } catch {
579
+ } finally {
580
+ this.activeFlushPromise = null;
581
+ if (this.queue.length > 0) {
582
+ void this.flush();
583
+ }
584
+ }
585
+ })();
586
+ return this.activeFlushPromise;
577
587
  }
578
588
  /**
579
589
  * Synchronous-ish flush for process shutdown.
@@ -664,7 +674,7 @@ function createQueryHandler(client, config) {
664
674
  if (trimmedQuery.length > 500) {
665
675
  return errorResponse(400, "QUERY_TOO_LONG", "Query must be 500 characters or fewer");
666
676
  }
667
- const requestId = crypto.randomUUID();
677
+ const requestId = globalThis.crypto.randomUUID();
668
678
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
669
679
  const startMs = Date.now();
670
680
  const surfaceAds = req.surface_ads !== false;
@@ -692,16 +702,21 @@ function createQueryHandler(client, config) {
692
702
  return errorResponse(502, "UPSTREAM_ERROR", "Could not retrieve an answer at this time");
693
703
  }
694
704
  const responseTimeMs = Date.now() - startMs;
695
- const ads = backendResponse.sponsored ? Array.isArray(backendResponse.sponsored) ? backendResponse.sponsored : [backendResponse.sponsored] : [];
696
- for (const ad of ads) {
697
- void client.logImpression({
698
- impression_id: ad.impression_id,
699
- site_id: config.siteId,
700
- query: trimmedQuery,
701
- agent_ua: req.userAgent,
702
- agent_ip: req.ipAddress,
703
- timestamp
704
- });
705
+ const ads = backendResponse.related_resources ? Array.isArray(backendResponse.related_resources) ? backendResponse.related_resources : [backendResponse.related_resources] : [];
706
+ if (ads.length > 0) {
707
+ await Promise.all(
708
+ ads.map(
709
+ (ad) => client.logImpression({
710
+ impression_id: ad.impression_id,
711
+ site_id: config.siteId,
712
+ query: trimmedQuery,
713
+ agent_ua: req.userAgent,
714
+ agent_ip: req.ipAddress,
715
+ timestamp
716
+ }).catch(() => {
717
+ })
718
+ )
719
+ );
705
720
  }
706
721
  const agentResponse = {
707
722
  success: true,
@@ -710,7 +725,7 @@ function createQueryHandler(client, config) {
710
725
  answer: backendResponse.answer,
711
726
  sources: backendResponse.sources,
712
727
  confidence: backendResponse.confidence,
713
- ...backendResponse.sponsored && { sponsored: backendResponse.sponsored },
728
+ ...backendResponse.related_resources && { related_resources: backendResponse.related_resources },
714
729
  metadata: {
715
730
  request_id: requestId,
716
731
  response_time_ms: responseTimeMs,
@@ -728,7 +743,7 @@ function errorResponse(status, code, message) {
728
743
  error: {
729
744
  code,
730
745
  message,
731
- request_id: crypto.randomUUID(),
746
+ request_id: globalThis.crypto.randomUUID(),
732
747
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
733
748
  }
734
749
  };
@@ -747,20 +762,49 @@ function getOrigin(url) {
747
762
  function createDashboardHandler(client, config) {
748
763
  return async function handleDashboard(req) {
749
764
  const { path, method, authHeader } = req;
750
- if (config.dashboardSecret) {
751
- const url2 = new URL(path, "http://localhost");
752
- const secretParam = url2.searchParams.get("secret");
753
- const bearerToken = authHeader?.startsWith("Bearer ") ? authHeader.substring(7) : null;
754
- const isAuthorized = secretParam === config.dashboardSecret || bearerToken === config.dashboardSecret;
755
- if (!isAuthorized) {
756
- return jsonResponse(401, {
757
- error: "Unauthorized",
758
- message: "Dashboard access requires a valid secret. Please set APPTVTY_DASHBOARD_SECRET."
759
- });
760
- }
761
- }
762
765
  const url = new URL(path, "http://localhost");
763
766
  const cleanPath = url.pathname;
767
+ if (cleanPath.includes("/api/apptvty/verify")) {
768
+ const challenge = url.searchParams.get("challenge");
769
+ if (!challenge) {
770
+ return jsonResponse(400, { error: "Challenge required" });
771
+ }
772
+ const encoder = new TextEncoder();
773
+ const keyData = encoder.encode(config.apiKey);
774
+ const cryptoKey = await globalThis.crypto.subtle.importKey(
775
+ "raw",
776
+ keyData,
777
+ { name: "HMAC", hash: "SHA-256" },
778
+ false,
779
+ ["sign"]
780
+ );
781
+ const prefixedChallenge = `apptvty_verify_challenge:${challenge}`;
782
+ const signatureBuffer = await globalThis.crypto.subtle.sign("HMAC", cryptoKey, encoder.encode(prefixedChallenge));
783
+ const signature = Array.from(new Uint8Array(signatureBuffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
784
+ return jsonResponse(200, {
785
+ site_id: config.siteId,
786
+ verified: true,
787
+ signature
788
+ });
789
+ }
790
+ if (!config.dashboardSecret) {
791
+ return jsonResponse(401, {
792
+ error: "Unauthorized",
793
+ message: "Dashboard access is disabled because APPTVTY_DASHBOARD_SECRET is not configured."
794
+ });
795
+ }
796
+ const secretParam = url.searchParams.get("secret");
797
+ const bearerToken = authHeader?.startsWith("Bearer ") ? authHeader.substring(7) : null;
798
+ const isAuthorized = secretParam === config.dashboardSecret || bearerToken === config.dashboardSecret;
799
+ if (!isAuthorized) {
800
+ return jsonResponse(401, {
801
+ error: "Unauthorized",
802
+ message: "Invalid dashboard secret. Please provide a valid secret to access."
803
+ });
804
+ }
805
+ if (config.debug) {
806
+ console.error(`[apptvty] DashboardHandler: path=${path}, cleanPath=${cleanPath}`);
807
+ }
764
808
  if (cleanPath.includes("/api/overview")) {
765
809
  const data = await client.getSiteStats();
766
810
  return jsonResponse(200, data);
@@ -1191,13 +1235,13 @@ function createExpressMiddleware(config) {
1191
1235
  const entry = {
1192
1236
  site_id: config.siteId,
1193
1237
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1194
- method: req.method ?? "GET",
1195
- path,
1196
- status_code: res.statusCode,
1238
+ request_method: req.method ?? "GET",
1239
+ request_path: path,
1240
+ response_status: res.statusCode,
1197
1241
  response_time_ms: Date.now() - startMs,
1198
1242
  ip_address: ipAddress,
1199
1243
  user_agent: userAgent,
1200
- referer: req.headers["referer"] ?? null,
1244
+ referrer: req.headers["referer"] ?? null,
1201
1245
  is_ai_crawler: crawlerInfo.isAi,
1202
1246
  crawler_type: crawlerInfo.name,
1203
1247
  crawler_organization: crawlerInfo.organization,