@dropout-ai/runtime 0.3.4 → 0.3.5

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/index.js +12 -27
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dropout-ai/runtime",
3
- "version": "0.3.4",
3
+ "version": "0.3.5",
4
4
  "description": "Invisible Node.js runtime for capturing AI interactions.",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
package/src/index.js CHANGED
@@ -20,6 +20,14 @@ const KNOWN_AI_DOMAINS = [
20
20
 
21
21
  class Dropout {
22
22
  constructor(config = {}) {
23
+ // 🚨 BROWSER GUARD 🚨
24
+ // If this runs in the browser (Client Component), do nothing.
25
+ // This prevents the "listener" error and protects your API keys.
26
+ if (typeof window !== 'undefined') {
27
+ console.warn("[Dropout] ⚠️ Initialization Skipped: This SDK is for Node.js/Server environments only.");
28
+ return;
29
+ }
30
+
23
31
  // 1. Validation
24
32
  if (!config.apiKey || !config.projectId) {
25
33
  console.warn("[Dropout] ⚠️ Initialization Skipped: Missing apiKey or projectId.");
@@ -49,7 +57,7 @@ class Dropout {
49
57
  // 5. Start the Wiretap
50
58
  if (this.debug) console.log(`[Dropout] 🟢 Initialized for Project: ${this.projectId}`);
51
59
 
52
- // Bind methods to 'this' to avoid scope loss
60
+ // Bind methods
53
61
  this.log = this.log.bind(this);
54
62
  this.isAiRequest = this.isAiRequest.bind(this);
55
63
  this.patchNetwork();
@@ -101,13 +109,10 @@ class Dropout {
101
109
 
102
110
  isAiRequest(url, bodyString) {
103
111
  if (!url) return false;
104
- // Guard: Infinite Loop Prevention
105
112
  if (url.includes(this.captureEndpoint)) return false;
106
113
 
107
- // 1. Check Known Domains
108
114
  if (KNOWN_AI_DOMAINS.some(d => url.includes(d))) return true;
109
115
 
110
- // 2. Heuristic Check (for custom endpoints)
111
116
  if (bodyString) {
112
117
  return (bodyString.includes('"model"') || bodyString.includes('"messages"')) &&
113
118
  (bodyString.includes('"user"') || bodyString.includes('"prompt"'));
@@ -117,22 +122,18 @@ class Dropout {
117
122
 
118
123
  // --- EMITTER ---
119
124
  emit(payload) {
120
- // 1. Guard
121
125
  if (!this.apiKey || !this.projectId) return;
122
126
 
123
- // 2. Construct Payload
124
127
  const finalPayload = {
125
128
  ...payload,
126
129
  project_id: this.projectId,
127
- content_blob: payload.content, // Map for new schema
128
- content: payload.content, // Map for old schema (fallback)
130
+ //content_blob: payload.content,
131
+ content: payload.content,
129
132
  received_at: new Date().toISOString()
130
133
  };
131
134
 
132
135
  this.log(`🚀 Sending Capture (${payload.turn_role})`);
133
136
 
134
- // 3. Fire and Forget using HTTPS (Bypassing our own patches via pure request)
135
- // We use a clean request to avoid triggering our own hooks if we used global.fetch
136
137
  const req = https.request(this.captureEndpoint, {
137
138
  method: 'POST',
138
139
  headers: {
@@ -148,7 +149,7 @@ class Dropout {
148
149
 
149
150
  // --- CORE PATCHING ---
150
151
  patchNetwork() {
151
- const _this = this; // Capture instance
152
+ const _this = this;
152
153
 
153
154
  // --- A. PATCH FETCH ---
154
155
  if (global.fetch) {
@@ -156,13 +157,11 @@ class Dropout {
156
157
  global.fetch = async function (input, init) {
157
158
  const url = typeof input === 'string' ? input : input?.url;
158
159
 
159
- // Safe Body Check
160
160
  let bodyStr = "";
161
161
  if (init && init.body) {
162
162
  try { bodyStr = typeof init.body === 'string' ? init.body : JSON.stringify(init.body); } catch (e) { }
163
163
  }
164
164
 
165
- // Guard
166
165
  if (!_this.isAiRequest(url, bodyStr)) {
167
166
  return originalFetch.apply(this, arguments);
168
167
  }
@@ -170,7 +169,6 @@ class Dropout {
170
169
  _this.log(`⚡ [FETCH] Intercepting: ${url}`);
171
170
  const start = Date.now();
172
171
 
173
- // Emit Request
174
172
  const { provider, model } = _this.normalize(url, bodyStr);
175
173
  _this.emit({
176
174
  session_id: global.__dropout_session_id__,
@@ -182,7 +180,6 @@ class Dropout {
182
180
  metadata_flags: { method: 'FETCH', url: url }
183
181
  });
184
182
 
185
- // Execute
186
183
  let response;
187
184
  try {
188
185
  response = await originalFetch.apply(this, arguments);
@@ -190,7 +187,6 @@ class Dropout {
190
187
 
191
188
  const latency = Date.now() - start;
192
189
 
193
- // Emit Response
194
190
  try {
195
191
  const cloned = response.clone();
196
192
  const oText = await cloned.text();
@@ -216,7 +212,6 @@ class Dropout {
216
212
  const originalRequest = module.request;
217
213
  module.request = function (...args) {
218
214
  let url;
219
- // Argument Resolution (url, options, callback) or (options, callback)
220
215
  if (typeof args[0] === 'string') {
221
216
  url = args[0];
222
217
  } else {
@@ -235,35 +230,26 @@ class Dropout {
235
230
  const start = Date.now();
236
231
 
237
232
  const clientRequest = originalRequest.apply(this, args);
238
-
239
- // Capture Buffers
240
233
  const reqChunks = [];
241
234
  const originalWrite = clientRequest.write;
242
235
  const originalEnd = clientRequest.end;
243
236
 
244
- // SAFE WRITE PATCH
245
237
  clientRequest.write = function (...writeArgs) {
246
238
  const chunk = writeArgs[0];
247
- // Only buffer if it's data (String or Buffer), ignore objects/callbacks
248
239
  if (chunk && (typeof chunk === 'string' || Buffer.isBuffer(chunk))) {
249
240
  reqChunks.push(Buffer.from(chunk));
250
241
  }
251
242
  return originalWrite.apply(this, writeArgs);
252
243
  };
253
244
 
254
- // SAFE END PATCH
255
245
  clientRequest.end = function (...endArgs) {
256
246
  const chunk = endArgs[0];
257
- // Only buffer if it's data
258
247
  if (chunk && (typeof chunk === 'string' || Buffer.isBuffer(chunk))) {
259
248
  reqChunks.push(Buffer.from(chunk));
260
249
  }
261
250
 
262
- // Emit Request
263
251
  const reqBody = Buffer.concat(reqChunks).toString('utf8');
264
252
  const { provider, model } = _this.normalize(url, reqBody);
265
-
266
- // Attach state to request for response handler
267
253
  clientRequest._dropout_meta = { provider, model };
268
254
 
269
255
  _this.emit({
@@ -279,7 +265,6 @@ class Dropout {
279
265
  return originalEnd.apply(this, endArgs);
280
266
  };
281
267
 
282
- // Capture Response
283
268
  clientRequest.on('response', (res) => {
284
269
  const resChunks = [];
285
270
  res.on('data', (chunk) => resChunks.push(chunk));