@adwait12345/telemetry-core 0.1.2 → 0.1.4
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/README.md +127 -0
- package/dist/index.d.mts +13 -1
- package/dist/index.d.ts +13 -1
- package/dist/index.js +59 -29
- package/dist/index.mjs +59 -29
- package/package.json +3 -2
package/README.md
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# `@adwait12345/telemetry-core`
|
|
2
|
+
|
|
3
|
+
The shared core of the Telemetry SDK. Contains bot detection logic, the send function with retry support, and all shared TypeScript types used by the framework adapters (`telemetry-next`, `telemetry-express`, `telemetry-astro`).
|
|
4
|
+
|
|
5
|
+
You typically do **not** install this directly — install the adapter for your framework instead. Use this package if you are building a custom adapter.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @adwait12345/telemetry-core
|
|
13
|
+
# or
|
|
14
|
+
pnpm add @adwait12345/telemetry-core
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## What's included
|
|
20
|
+
|
|
21
|
+
### `detectBot(req, customBots?)`
|
|
22
|
+
|
|
23
|
+
Runs multi-layer bot detection against a normalized request object.
|
|
24
|
+
|
|
25
|
+
**Detection layers (in priority order):**
|
|
26
|
+
|
|
27
|
+
| Layer | Method | Confidence |
|
|
28
|
+
|-------|--------|------------|
|
|
29
|
+
| 1 | Automation headers (`x-selenium`, `x-puppeteer`, `x-playwright`, etc.) | `certain` |
|
|
30
|
+
| 2 | HTTP/1.0 — no modern browser uses this | `high` |
|
|
31
|
+
| 3 | Named bot UA match (100+ known bots across 16 categories) | `certain` |
|
|
32
|
+
| 4 | Generic bot UA patterns (`/bot/i`, `/crawler/i`, empty/short UA, etc.) | `high` |
|
|
33
|
+
| 5 | Header anomaly — claims modern browser but missing `sec-fetch-site` / `accept-language` | `medium`–`high` |
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
import { detectBot } from "@adwait12345/telemetry-core";
|
|
37
|
+
|
|
38
|
+
const result = detectBot({
|
|
39
|
+
userAgent: "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)",
|
|
40
|
+
ip: "66.249.66.1",
|
|
41
|
+
path: "/",
|
|
42
|
+
method: "GET",
|
|
43
|
+
referrer: null,
|
|
44
|
+
acceptLanguage: null,
|
|
45
|
+
acceptEncoding: "gzip",
|
|
46
|
+
secFetchSite: null,
|
|
47
|
+
httpVersion: "1.1",
|
|
48
|
+
automationHeaders: [],
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// result.isBot → true
|
|
52
|
+
// result.botName → "Googlebot"
|
|
53
|
+
// result.botCategory → "search"
|
|
54
|
+
// result.confidence → "certain"
|
|
55
|
+
// result.method → "ua-match"
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### `extractAutomationHeaders(headers)`
|
|
59
|
+
|
|
60
|
+
Checks a plain headers object for known automation tool headers. Returns the list of matched header names. Call this before `detectBot` to populate `automationHeaders`.
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
import { extractAutomationHeaders } from "@adwait12345/telemetry-core";
|
|
64
|
+
|
|
65
|
+
const matched = extractAutomationHeaders(req.headers);
|
|
66
|
+
// e.g. ["x-playwright"] if Playwright is driving the browser
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### `sendToTelemetry(payload, config)`
|
|
70
|
+
|
|
71
|
+
Sends a tracking payload to the Telemetry API. Includes:
|
|
72
|
+
- **3 attempts** with exponential backoff (200 ms → 400 ms)
|
|
73
|
+
- **5 second timeout** per attempt via `AbortController`
|
|
74
|
+
- **4xx errors skip retry** (they will not succeed on retry)
|
|
75
|
+
- Automatic `Authorization: Bearer {serverSecret}` header
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
import { sendToTelemetry } from "@adwait12345/telemetry-core";
|
|
79
|
+
|
|
80
|
+
await sendToTelemetry(payload, {
|
|
81
|
+
projectId: "your-project-id",
|
|
82
|
+
apiUrl: "https://telemetry-uqd3.onrender.com",
|
|
83
|
+
serverSecret: "sk_...",
|
|
84
|
+
});
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## Bot categories
|
|
90
|
+
|
|
91
|
+
The 100+ built-in bots are organized into 16 categories:
|
|
92
|
+
|
|
93
|
+
| Category | Examples |
|
|
94
|
+
|----------|---------|
|
|
95
|
+
| `ai-crawler` | GPTBot, ClaudeBot, PerplexityBot, Applebot |
|
|
96
|
+
| `ai-assistant` | ChatGPT-User, Claude-Web, Copilot |
|
|
97
|
+
| `search` | Googlebot, Bingbot, DuckDuckBot, Yandex |
|
|
98
|
+
| `seo` | AhrefsBot, SemrushBot, MajesticSEO |
|
|
99
|
+
| `advertising` | Mediapartners-Google, AdsBot-Google |
|
|
100
|
+
| `monitor` | UptimeRobot, Pingdom, StatusCake |
|
|
101
|
+
| `preview` | Slackbot, Twitterbot, facebookexternalhit |
|
|
102
|
+
| `webhook` | Stripe-Webhook, GitHub-Hookshot, Shopify |
|
|
103
|
+
| `feed` | Feedly, Inoreader, NewsBlur |
|
|
104
|
+
| `ecommerce` | Shopify, Wix |
|
|
105
|
+
| `verification` | Let's Encrypt, SSL Labs |
|
|
106
|
+
| `analytics` | New Relic, Datadog |
|
|
107
|
+
| `social` | LinkedInBot, Pinterest |
|
|
108
|
+
| `accessibility` | ChromeVox |
|
|
109
|
+
| `scraper` | Scrapy, HTTrack |
|
|
110
|
+
| `unknown` | Anything matching generic bot patterns |
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## `TelemetryConfig` reference
|
|
115
|
+
|
|
116
|
+
| Option | Type | Default | Description |
|
|
117
|
+
|--------|------|---------|-------------|
|
|
118
|
+
| `projectId` | `string` | **required** | Your Telemetry project ID |
|
|
119
|
+
| `apiUrl` | `string` | hosted service | API base URL |
|
|
120
|
+
| `serverSecret` | `string` | — | Secret key for server-side auth (`Bearer {serverSecret}`) |
|
|
121
|
+
| `authorizationHeader` | `string` | — | Fully custom `Authorization` header value, overrides `serverSecret` |
|
|
122
|
+
| `headers` | `Record<string, string>` | — | Extra headers merged into every telemetry request |
|
|
123
|
+
| `trackAll` | `boolean` | `false` | Track all requests, not just bots |
|
|
124
|
+
| `trackSearchBots` | `boolean` | `true` | Include Googlebot, Bingbot etc. |
|
|
125
|
+
| `ignorePaths` | `(string \| RegExp)[]` | — | Paths to skip (exact strings or regex) |
|
|
126
|
+
| `customBots` | `Array<{name, pattern, category?}>` | — | Add your own bot definitions |
|
|
127
|
+
| `debug` | `boolean` | `false` | Enable verbose console logging |
|
package/dist/index.d.mts
CHANGED
|
@@ -65,6 +65,18 @@ interface TelemetryConfig {
|
|
|
65
65
|
* This is required since these requests originate from the server and cannot pass domain validation checks via Origin.
|
|
66
66
|
*/
|
|
67
67
|
serverSecret?: string;
|
|
68
|
+
/**
|
|
69
|
+
* Fully custom Authorization header value.
|
|
70
|
+
* Overrides the default "Bearer {serverSecret}" format.
|
|
71
|
+
* e.g. "Token mytoken" or "Basic dXNlcjpwYXNz"
|
|
72
|
+
*/
|
|
73
|
+
authorizationHeader?: string;
|
|
74
|
+
/**
|
|
75
|
+
* Additional custom headers to send with every telemetry request.
|
|
76
|
+
* These are merged on top of the defaults and can override them.
|
|
77
|
+
* e.g. { "x-workspace-id": "abc123" }
|
|
78
|
+
*/
|
|
79
|
+
headers?: Record<string, string>;
|
|
68
80
|
}
|
|
69
81
|
/** Payload sent to the Telemetry server-side tracking endpoint */
|
|
70
82
|
interface ServerTrackPayload {
|
|
@@ -142,7 +154,7 @@ declare const ALL_BOTS: BotDefinition[];
|
|
|
142
154
|
|
|
143
155
|
/**
|
|
144
156
|
* Sends a server-side payload to the Telemetry backend.
|
|
145
|
-
*
|
|
157
|
+
* Automatically retries on transient failures with exponential backoff.
|
|
146
158
|
*
|
|
147
159
|
* @param payload The data to track.
|
|
148
160
|
* @param config The Telemetry configuration.
|
package/dist/index.d.ts
CHANGED
|
@@ -65,6 +65,18 @@ interface TelemetryConfig {
|
|
|
65
65
|
* This is required since these requests originate from the server and cannot pass domain validation checks via Origin.
|
|
66
66
|
*/
|
|
67
67
|
serverSecret?: string;
|
|
68
|
+
/**
|
|
69
|
+
* Fully custom Authorization header value.
|
|
70
|
+
* Overrides the default "Bearer {serverSecret}" format.
|
|
71
|
+
* e.g. "Token mytoken" or "Basic dXNlcjpwYXNz"
|
|
72
|
+
*/
|
|
73
|
+
authorizationHeader?: string;
|
|
74
|
+
/**
|
|
75
|
+
* Additional custom headers to send with every telemetry request.
|
|
76
|
+
* These are merged on top of the defaults and can override them.
|
|
77
|
+
* e.g. { "x-workspace-id": "abc123" }
|
|
78
|
+
*/
|
|
79
|
+
headers?: Record<string, string>;
|
|
68
80
|
}
|
|
69
81
|
/** Payload sent to the Telemetry server-side tracking endpoint */
|
|
70
82
|
interface ServerTrackPayload {
|
|
@@ -142,7 +154,7 @@ declare const ALL_BOTS: BotDefinition[];
|
|
|
142
154
|
|
|
143
155
|
/**
|
|
144
156
|
* Sends a server-side payload to the Telemetry backend.
|
|
145
|
-
*
|
|
157
|
+
* Automatically retries on transient failures with exponential backoff.
|
|
146
158
|
*
|
|
147
159
|
* @param payload The data to track.
|
|
148
160
|
* @param config The Telemetry configuration.
|
package/dist/index.js
CHANGED
|
@@ -1112,10 +1112,67 @@ function extractAutomationHeaders(headers) {
|
|
|
1112
1112
|
}
|
|
1113
1113
|
|
|
1114
1114
|
// src/index.ts
|
|
1115
|
+
function buildHeaders(config) {
|
|
1116
|
+
const headers = {
|
|
1117
|
+
"Content-Type": "application/json"
|
|
1118
|
+
};
|
|
1119
|
+
if (config.authorizationHeader) {
|
|
1120
|
+
headers["Authorization"] = config.authorizationHeader;
|
|
1121
|
+
} else if (config.serverSecret) {
|
|
1122
|
+
headers["Authorization"] = `Bearer ${config.serverSecret}`;
|
|
1123
|
+
}
|
|
1124
|
+
if (config.headers) {
|
|
1125
|
+
Object.assign(headers, config.headers);
|
|
1126
|
+
}
|
|
1127
|
+
return headers;
|
|
1128
|
+
}
|
|
1129
|
+
async function sendWithRetry(endpoint, payload, config, retries = 2) {
|
|
1130
|
+
const headers = buildHeaders(config);
|
|
1131
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
1132
|
+
const controller = new AbortController();
|
|
1133
|
+
const timeout = setTimeout(() => controller.abort(), 5e3);
|
|
1134
|
+
try {
|
|
1135
|
+
const response = await fetch(endpoint, {
|
|
1136
|
+
method: "POST",
|
|
1137
|
+
headers,
|
|
1138
|
+
body: JSON.stringify(payload),
|
|
1139
|
+
signal: controller.signal
|
|
1140
|
+
});
|
|
1141
|
+
clearTimeout(timeout);
|
|
1142
|
+
if (response.ok) return;
|
|
1143
|
+
if (config.debug) {
|
|
1144
|
+
const text = await response.text();
|
|
1145
|
+
console.error(
|
|
1146
|
+
`[Telemetry] Attempt ${attempt + 1} failed: ${response.status} \u2014 ${text}`
|
|
1147
|
+
);
|
|
1148
|
+
}
|
|
1149
|
+
if (response.status >= 400 && response.status < 500) return;
|
|
1150
|
+
} catch (error) {
|
|
1151
|
+
clearTimeout(timeout);
|
|
1152
|
+
if (config.debug) {
|
|
1153
|
+
if (error?.name === "AbortError") {
|
|
1154
|
+
console.error(
|
|
1155
|
+
`[Telemetry] Attempt ${attempt + 1} timed out after 5s.`
|
|
1156
|
+
);
|
|
1157
|
+
} else {
|
|
1158
|
+
console.error(`[Telemetry] Attempt ${attempt + 1} error:`, error);
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
if (attempt < retries) {
|
|
1163
|
+
await new Promise((r) => setTimeout(r, 200 * Math.pow(2, attempt)));
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
if (config.debug) {
|
|
1167
|
+
console.error("[Telemetry] All retry attempts failed \u2014 giving up.");
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1115
1170
|
async function sendToTelemetry(payload, config) {
|
|
1116
1171
|
if (!config.apiUrl) {
|
|
1117
1172
|
if (config.debug) {
|
|
1118
|
-
console.warn(
|
|
1173
|
+
console.warn(
|
|
1174
|
+
"[Telemetry] No apiUrl configured \u2014 skipping telemetry send."
|
|
1175
|
+
);
|
|
1119
1176
|
}
|
|
1120
1177
|
return;
|
|
1121
1178
|
}
|
|
@@ -1123,34 +1180,7 @@ async function sendToTelemetry(payload, config) {
|
|
|
1123
1180
|
if (config.debug) {
|
|
1124
1181
|
console.log(`[Telemetry] Sending payload to ${endpoint}`, payload);
|
|
1125
1182
|
}
|
|
1126
|
-
|
|
1127
|
-
const timeout = setTimeout(() => controller.abort(), 3e3);
|
|
1128
|
-
try {
|
|
1129
|
-
const response = await fetch(endpoint, {
|
|
1130
|
-
method: "POST",
|
|
1131
|
-
headers: {
|
|
1132
|
-
"Content-Type": "application/json",
|
|
1133
|
-
"Authorization": `Bearer ${config.serverSecret}`
|
|
1134
|
-
},
|
|
1135
|
-
body: JSON.stringify(payload),
|
|
1136
|
-
signal: controller.signal
|
|
1137
|
-
});
|
|
1138
|
-
if (!response.ok && config.debug) {
|
|
1139
|
-
console.error(`[Telemetry] Failed to send payload: ${response.status} ${response.statusText}`);
|
|
1140
|
-
const text = await response.text();
|
|
1141
|
-
console.error(`[Telemetry] Response body:`, text);
|
|
1142
|
-
}
|
|
1143
|
-
} catch (error) {
|
|
1144
|
-
if (config.debug) {
|
|
1145
|
-
if (error?.name === "AbortError") {
|
|
1146
|
-
console.error("[Telemetry] Request timed out after 3s \u2014 skipping.");
|
|
1147
|
-
} else {
|
|
1148
|
-
console.error("[Telemetry] Error sending payload:", error);
|
|
1149
|
-
}
|
|
1150
|
-
}
|
|
1151
|
-
} finally {
|
|
1152
|
-
clearTimeout(timeout);
|
|
1153
|
-
}
|
|
1183
|
+
await sendWithRetry(endpoint, payload, config);
|
|
1154
1184
|
}
|
|
1155
1185
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1156
1186
|
0 && (module.exports = {
|
package/dist/index.mjs
CHANGED
|
@@ -1068,10 +1068,67 @@ function extractAutomationHeaders(headers) {
|
|
|
1068
1068
|
}
|
|
1069
1069
|
|
|
1070
1070
|
// src/index.ts
|
|
1071
|
+
function buildHeaders(config) {
|
|
1072
|
+
const headers = {
|
|
1073
|
+
"Content-Type": "application/json"
|
|
1074
|
+
};
|
|
1075
|
+
if (config.authorizationHeader) {
|
|
1076
|
+
headers["Authorization"] = config.authorizationHeader;
|
|
1077
|
+
} else if (config.serverSecret) {
|
|
1078
|
+
headers["Authorization"] = `Bearer ${config.serverSecret}`;
|
|
1079
|
+
}
|
|
1080
|
+
if (config.headers) {
|
|
1081
|
+
Object.assign(headers, config.headers);
|
|
1082
|
+
}
|
|
1083
|
+
return headers;
|
|
1084
|
+
}
|
|
1085
|
+
async function sendWithRetry(endpoint, payload, config, retries = 2) {
|
|
1086
|
+
const headers = buildHeaders(config);
|
|
1087
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
1088
|
+
const controller = new AbortController();
|
|
1089
|
+
const timeout = setTimeout(() => controller.abort(), 5e3);
|
|
1090
|
+
try {
|
|
1091
|
+
const response = await fetch(endpoint, {
|
|
1092
|
+
method: "POST",
|
|
1093
|
+
headers,
|
|
1094
|
+
body: JSON.stringify(payload),
|
|
1095
|
+
signal: controller.signal
|
|
1096
|
+
});
|
|
1097
|
+
clearTimeout(timeout);
|
|
1098
|
+
if (response.ok) return;
|
|
1099
|
+
if (config.debug) {
|
|
1100
|
+
const text = await response.text();
|
|
1101
|
+
console.error(
|
|
1102
|
+
`[Telemetry] Attempt ${attempt + 1} failed: ${response.status} \u2014 ${text}`
|
|
1103
|
+
);
|
|
1104
|
+
}
|
|
1105
|
+
if (response.status >= 400 && response.status < 500) return;
|
|
1106
|
+
} catch (error) {
|
|
1107
|
+
clearTimeout(timeout);
|
|
1108
|
+
if (config.debug) {
|
|
1109
|
+
if (error?.name === "AbortError") {
|
|
1110
|
+
console.error(
|
|
1111
|
+
`[Telemetry] Attempt ${attempt + 1} timed out after 5s.`
|
|
1112
|
+
);
|
|
1113
|
+
} else {
|
|
1114
|
+
console.error(`[Telemetry] Attempt ${attempt + 1} error:`, error);
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
if (attempt < retries) {
|
|
1119
|
+
await new Promise((r) => setTimeout(r, 200 * Math.pow(2, attempt)));
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
if (config.debug) {
|
|
1123
|
+
console.error("[Telemetry] All retry attempts failed \u2014 giving up.");
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1071
1126
|
async function sendToTelemetry(payload, config) {
|
|
1072
1127
|
if (!config.apiUrl) {
|
|
1073
1128
|
if (config.debug) {
|
|
1074
|
-
console.warn(
|
|
1129
|
+
console.warn(
|
|
1130
|
+
"[Telemetry] No apiUrl configured \u2014 skipping telemetry send."
|
|
1131
|
+
);
|
|
1075
1132
|
}
|
|
1076
1133
|
return;
|
|
1077
1134
|
}
|
|
@@ -1079,34 +1136,7 @@ async function sendToTelemetry(payload, config) {
|
|
|
1079
1136
|
if (config.debug) {
|
|
1080
1137
|
console.log(`[Telemetry] Sending payload to ${endpoint}`, payload);
|
|
1081
1138
|
}
|
|
1082
|
-
|
|
1083
|
-
const timeout = setTimeout(() => controller.abort(), 3e3);
|
|
1084
|
-
try {
|
|
1085
|
-
const response = await fetch(endpoint, {
|
|
1086
|
-
method: "POST",
|
|
1087
|
-
headers: {
|
|
1088
|
-
"Content-Type": "application/json",
|
|
1089
|
-
"Authorization": `Bearer ${config.serverSecret}`
|
|
1090
|
-
},
|
|
1091
|
-
body: JSON.stringify(payload),
|
|
1092
|
-
signal: controller.signal
|
|
1093
|
-
});
|
|
1094
|
-
if (!response.ok && config.debug) {
|
|
1095
|
-
console.error(`[Telemetry] Failed to send payload: ${response.status} ${response.statusText}`);
|
|
1096
|
-
const text = await response.text();
|
|
1097
|
-
console.error(`[Telemetry] Response body:`, text);
|
|
1098
|
-
}
|
|
1099
|
-
} catch (error) {
|
|
1100
|
-
if (config.debug) {
|
|
1101
|
-
if (error?.name === "AbortError") {
|
|
1102
|
-
console.error("[Telemetry] Request timed out after 3s \u2014 skipping.");
|
|
1103
|
-
} else {
|
|
1104
|
-
console.error("[Telemetry] Error sending payload:", error);
|
|
1105
|
-
}
|
|
1106
|
-
}
|
|
1107
|
-
} finally {
|
|
1108
|
-
clearTimeout(timeout);
|
|
1109
|
-
}
|
|
1139
|
+
await sendWithRetry(endpoint, payload, config);
|
|
1110
1140
|
}
|
|
1111
1141
|
export {
|
|
1112
1142
|
ACCESSIBILITY_BOTS,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adwait12345/telemetry-core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Framework-agnostic core for Telemetry SDK — bot detection and server-side tracking",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
}
|
|
14
14
|
},
|
|
15
15
|
"files": [
|
|
16
|
-
"dist"
|
|
16
|
+
"dist",
|
|
17
|
+
"README.md"
|
|
17
18
|
],
|
|
18
19
|
"devDependencies": {
|
|
19
20
|
"tsup": "^8.0.0",
|