log-inject 0.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Maifee Ul Asad
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,167 @@
1
+ # log-inject
2
+
3
+ A **production-grade TypeScript console interceptor** that:
4
+
5
+ - Wraps **every** `console.*` method (all 22 in the MDN spec)
6
+ - Forwards batched log entries to your backend via `POST`
7
+ - Preserves native DevTools behaviour (passthrough)
8
+ - Persists a session-id via **localStorage** or a **non-tracking cookie** (your choice)
9
+ - Guards against missing methods on old browsers (backward compatibility polyfill)
10
+ - Survives page unloads via `sendBeacon` + synchronous XHR fallback
11
+ - Ships as a single **7 kB minified** browser bundle
12
+
13
+ ---
14
+
15
+ ## Project structure
16
+
17
+ ```
18
+ log-inject/
19
+ ├── src/
20
+ │ ├── types.ts — All TypeScript interfaces & type aliases
21
+ │ ├── session.ts — Session-id persistence (localStorage / cookie / none)
22
+ │ ├── serializer.ts — Safe arg serialisation + stack-trace capture
23
+ │ ├── transport.ts — Batched fetch + XHR fallback + sendBeacon unload flush
24
+ │ ├── patch.ts — Core interception logic for all 22 console methods
25
+ │ └── index.ts — Public barrel + data-attribute auto-installer
26
+ ├── dist/
27
+ │ ├── log-inject.js — Unminified bundle (with source map)
28
+ │ └── log-inject.min.js — Minified bundle (7 kB)
29
+ ├── example.html — Interactive demo page
30
+ ├── tsconfig.json
31
+ └── package.json
32
+ ```
33
+
34
+ ---
35
+
36
+ ## Quick start
37
+
38
+ ### Method 1 — data-attribute auto-install (zero JS)
39
+
40
+ Drop one `<script>` tag at the **top of your `<head>`**, before any other scripts:
41
+
42
+ ```html
43
+ <script
44
+ src="/log-inject.min.js"
45
+ data-endpoint="/api/console-logs"
46
+ data-methods="log,info,warn,error,debug,trace"
47
+ data-storage="localStorage"
48
+ data-flush-interval="3000"
49
+ ></script>
50
+ ```
51
+
52
+ That's it. Every `console.*` call from that point is captured and batched.
53
+
54
+ ### Method 2 — programmatic install
55
+
56
+ ```html
57
+ <script src="/log-inject.min.js"></script>
58
+ <script>
59
+ ConsolePatch.install({
60
+ endpoint: '/api/console-logs',
61
+
62
+ // Add auth / correlation headers
63
+ headers: { 'Authorization': 'Bearer ' + getToken() },
64
+
65
+ // Limit to only these methods in production
66
+ methods: ['log', 'info', 'warn', 'error', 'assert', 'trace'],
67
+
68
+ // Keep native DevTools output
69
+ passthrough: true,
70
+
71
+ // Use a non-tracking session cookie instead of localStorage
72
+ storageType: 'cookie',
73
+ cookieOptions: {
74
+ maxAgeDays: 30,
75
+ sameSite: 'Strict',
76
+ secure: true,
77
+ },
78
+
79
+ flushInterval: 5000, // ms between flushes
80
+ maxQueueSize: 200, // immediate flush threshold
81
+ maxArgLength: 4000, // truncate long strings
82
+
83
+ onFlush(entries) {
84
+ console.debug('[polyfill] flushed', entries.length, 'entries');
85
+ },
86
+ onFlushError(err, entries) {
87
+ console.error('[polyfill] flush failed:', err.message);
88
+ },
89
+ });
90
+ </script>
91
+ ```
92
+
93
+ ---
94
+
95
+ ## Backend payload
96
+
97
+ Each `POST` to your endpoint carries:
98
+
99
+ ```json
100
+ {
101
+ "logs": [
102
+ {
103
+ "id": "1715510000000-1",
104
+ "method": "error",
105
+ "level": "error",
106
+ "timestamp": "2026-05-12T10:00:00.000Z",
107
+ "timestampMs": 1715510000000,
108
+ "url": "https://yourapp.com/dashboard",
109
+ "userAgent": "Mozilla/5.0 …",
110
+ "sessionId": "a1b2c3d4-…",
111
+ "args": ["Uncaught TypeError", "Cannot read properties of null"],
112
+ "stack": "TypeError: Cannot read …\n at foo (app.js:42:7)\n …",
113
+ "groupDepth": 0
114
+ }
115
+ ]
116
+ }
117
+ ```
118
+
119
+ ---
120
+
121
+ ## Configuration reference
122
+
123
+ | Option | Type | Default | Description |
124
+ |---|---|---|---|
125
+ | `endpoint` | `string \| null` | `'/api/console-logs'` | POST URL. `null` disables remote shipping. |
126
+ | `headers` | `Record<string,string>` | `{}` | Extra HTTP headers (e.g. auth). |
127
+ | `methods` | `ConsoleMethod[]` | all 22 | Methods to intercept. |
128
+ | `passthrough` | `boolean` | `true` | Also forward to native DevTools. |
129
+ | `flushInterval` | `number` | `2000` | Milliseconds between batch flushes. |
130
+ | `maxQueueSize` | `number` | `50` | Immediate flush when queue exceeds this. |
131
+ | `sessionKey` | `string` | `'__cpoly_sid'` | Storage key for session-id. |
132
+ | `storageType` | `'localStorage' \| 'cookie' \| 'none'` | `'localStorage'` | Where to persist session-id. |
133
+ | `cookieOptions.maxAgeDays` | `number` | `365` | Cookie lifetime. |
134
+ | `cookieOptions.sameSite` | `'Strict' \| 'Lax' \| 'None'` | `'Strict'` | Cookie SameSite attribute. |
135
+ | `cookieOptions.secure` | `boolean` | `false` | Add Secure flag to cookie. |
136
+ | `maxArgLength` | `number` | `2000` | Truncate serialised args at this length. |
137
+ | `onFlush` | `(entries) => void` | — | Called after successful backend flush. |
138
+ | `onFlushError` | `(err, entries) => void` | — | Called on flush failure. |
139
+
140
+ ---
141
+
142
+ ## Build commands
143
+
144
+ ```bash
145
+ npm run build # typecheck + bundle (dev + min)
146
+ npm run typecheck # tsc --noEmit only
147
+ npm run dev # watch mode (unminified)
148
+ ```
149
+
150
+ ---
151
+
152
+ ## Backward compatibility
153
+
154
+ - All 22 console methods are **checked for existence** before wrapping.
155
+ - Methods absent in old browsers get a no-op shim so call sites don't throw.
156
+ - The bundle targets **ES2015** and works in all evergreen browsers.
157
+ - `fetch` unavailable → falls back to `XMLHttpRequest`.
158
+ - `sendBeacon` unavailable → falls back to synchronous XHR on page unload.
159
+
160
+ ---
161
+
162
+ ## Session tracking (non-tracking design)
163
+
164
+ The session-id is a random UUID stored **only in your own origin**
165
+ (`localStorage` or a `SameSite=Strict` cookie). It carries no PII and is
166
+ used solely to correlate log entries from the same browser tab session.
167
+ It is **not** shared with third parties.
@@ -0,0 +1,497 @@
1
+ "use strict";
2
+ (() => {
3
+ // session.ts
4
+ function generateId() {
5
+ var template = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx";
6
+ return template.replace(/[xy]/g, function(c) {
7
+ var r = Math.random() * 16 | 0;
8
+ var v = c === "x" ? r : r & 3 | 8;
9
+ return v.toString(16);
10
+ });
11
+ }
12
+ function lsGet(key) {
13
+ try {
14
+ return typeof localStorage !== "undefined" ? localStorage.getItem(key) : null;
15
+ } catch (_) {
16
+ return null;
17
+ }
18
+ }
19
+ function lsSet(key, value) {
20
+ try {
21
+ if (typeof localStorage !== "undefined") {
22
+ localStorage.setItem(key, value);
23
+ }
24
+ } catch (_) {
25
+ }
26
+ }
27
+ function cookieGet(name) {
28
+ try {
29
+ if (typeof document === "undefined") return null;
30
+ var pairs = document.cookie.split(";");
31
+ for (var i = 0; i < pairs.length; i++) {
32
+ var pair = pairs[i].trim().split("=");
33
+ if (pair[0] === name) {
34
+ return decodeURIComponent(pair[1] || "");
35
+ }
36
+ }
37
+ } catch (_) {
38
+ }
39
+ return null;
40
+ }
41
+ function cookieSet(name, value, maxAgeDays, sameSite, secure) {
42
+ try {
43
+ if (typeof document === "undefined") return;
44
+ var maxAge = maxAgeDays * 24 * 60 * 60;
45
+ var parts = [
46
+ name + "=" + encodeURIComponent(value),
47
+ "Max-Age=" + maxAge,
48
+ "SameSite=" + sameSite,
49
+ "Path=/"
50
+ ];
51
+ if (secure) parts.push("Secure");
52
+ document.cookie = parts.join("; ");
53
+ } catch (_) {
54
+ }
55
+ }
56
+ function resolveSessionId(config) {
57
+ var key = config.sessionKey || "__cpoly_sid";
58
+ var storageType = config.storageType || "localStorage";
59
+ var existing = null;
60
+ if (storageType === "localStorage") {
61
+ existing = lsGet(key);
62
+ } else if (storageType === "cookie") {
63
+ existing = cookieGet(key);
64
+ }
65
+ if (existing && existing.length > 0) {
66
+ return existing;
67
+ }
68
+ var id = generateId();
69
+ if (storageType === "localStorage") {
70
+ lsSet(key, id);
71
+ } else if (storageType === "cookie") {
72
+ var opts = config.cookieOptions || {};
73
+ cookieSet(
74
+ key,
75
+ id,
76
+ opts.maxAgeDays !== void 0 ? opts.maxAgeDays : 365,
77
+ opts.sameSite || "Strict",
78
+ opts.secure || false
79
+ );
80
+ }
81
+ return id;
82
+ }
83
+
84
+ // serializer.ts
85
+ function serializeArg(value, maxLength) {
86
+ var result;
87
+ if (value === null) {
88
+ result = "null";
89
+ } else if (value === void 0) {
90
+ result = "undefined";
91
+ } else if (typeof value === "function") {
92
+ result = "[Function: " + (value.name || "anonymous") + "]";
93
+ } else if (typeof value === "symbol") {
94
+ result = value.toString();
95
+ } else if (typeof value === "string") {
96
+ result = value;
97
+ } else if (typeof value === "number" || typeof value === "boolean") {
98
+ result = String(value);
99
+ } else if (value instanceof Error) {
100
+ result = value.name + ": " + value.message;
101
+ if (value.stack) {
102
+ result += "\n" + value.stack;
103
+ }
104
+ } else if (typeof HTMLElement !== "undefined" && value instanceof HTMLElement) {
105
+ result = value.outerHTML ? value.outerHTML.slice(0, 200) : "[HTMLElement: " + value.tagName + "]";
106
+ } else {
107
+ result = safeStringify(value);
108
+ }
109
+ if (result.length > maxLength) {
110
+ result = result.slice(0, maxLength) + " \u2026 [truncated]";
111
+ }
112
+ return result;
113
+ }
114
+ function safeStringify(value) {
115
+ var seen = [];
116
+ try {
117
+ return JSON.stringify(value, function(_key, val) {
118
+ if (typeof val === "object" && val !== null) {
119
+ if (seen.indexOf(val) !== -1) {
120
+ return "[Circular]";
121
+ }
122
+ seen.push(val);
123
+ }
124
+ if (typeof val === "bigint") {
125
+ return val.toString() + "n";
126
+ }
127
+ if (typeof val === "undefined") {
128
+ return "[undefined]";
129
+ }
130
+ return val;
131
+ }, 2);
132
+ } catch (e) {
133
+ return "[UnserializableObject]";
134
+ }
135
+ }
136
+ function captureStack(frameOffset) {
137
+ try {
138
+ var err = new Error();
139
+ if (!err.stack) return void 0;
140
+ var lines = err.stack.split("\n");
141
+ var start = frameOffset + 2;
142
+ return lines.slice(start).join("\n").trim() || void 0;
143
+ } catch (_) {
144
+ return void 0;
145
+ }
146
+ }
147
+
148
+ // transport.ts
149
+ function sendBatch(entries, config) {
150
+ var endpoint = config.endpoint;
151
+ if (!endpoint) return Promise.resolve();
152
+ var body = JSON.stringify({ logs: entries });
153
+ var headers = Object.assign(
154
+ { "Content-Type": "application/json" },
155
+ config.headers || {}
156
+ );
157
+ if (typeof fetch !== "undefined") {
158
+ return fetch(endpoint, {
159
+ method: "POST",
160
+ headers,
161
+ body,
162
+ // keepalive allows the request to outlive the page — useful for
163
+ // capturing errors that happen just before navigation.
164
+ keepalive: true
165
+ }).then(function(res) {
166
+ if (!res.ok) {
167
+ throw new Error("HTTP " + res.status + " from " + endpoint);
168
+ }
169
+ if (config.onFlush) config.onFlush(entries);
170
+ }).catch(function(err) {
171
+ if (config.onFlushError) config.onFlushError(err, entries);
172
+ });
173
+ }
174
+ return new Promise(function(resolve) {
175
+ try {
176
+ var xhr = new XMLHttpRequest();
177
+ xhr.open("POST", endpoint, true);
178
+ for (var key in headers) {
179
+ if (Object.prototype.hasOwnProperty.call(headers, key)) {
180
+ xhr.setRequestHeader(key, headers[key]);
181
+ }
182
+ }
183
+ xhr.onreadystatechange = function() {
184
+ if (xhr.readyState !== 4) return;
185
+ if (xhr.status >= 200 && xhr.status < 300) {
186
+ if (config.onFlush) config.onFlush(entries);
187
+ } else {
188
+ var err = new Error("XHR " + xhr.status + " from " + endpoint);
189
+ if (config.onFlushError) config.onFlushError(err, entries);
190
+ }
191
+ resolve();
192
+ };
193
+ xhr.send(body);
194
+ } catch (e) {
195
+ if (config.onFlushError) {
196
+ config.onFlushError(e instanceof Error ? e : new Error(String(e)), entries);
197
+ }
198
+ resolve();
199
+ }
200
+ });
201
+ }
202
+ function sendBeaconFallback(entries, endpoint) {
203
+ if (typeof navigator === "undefined" || !navigator.sendBeacon) return false;
204
+ try {
205
+ var blob = new Blob([JSON.stringify({ logs: entries })], {
206
+ type: "application/json"
207
+ });
208
+ return navigator.sendBeacon(endpoint, blob);
209
+ } catch (_) {
210
+ return false;
211
+ }
212
+ }
213
+
214
+ // patch.ts
215
+ var ALL_METHODS = [
216
+ "assert",
217
+ "clear",
218
+ "count",
219
+ "countReset",
220
+ "debug",
221
+ "dir",
222
+ "dirxml",
223
+ "error",
224
+ "group",
225
+ "groupCollapsed",
226
+ "groupEnd",
227
+ "info",
228
+ "log",
229
+ "profile",
230
+ "profileEnd",
231
+ "table",
232
+ "time",
233
+ "timeEnd",
234
+ "timeLog",
235
+ "timeStamp",
236
+ "trace",
237
+ "warn"
238
+ ];
239
+ var METHOD_TO_LEVEL = {
240
+ assert: "error",
241
+ clear: "log",
242
+ count: "log",
243
+ countReset: "log",
244
+ debug: "debug",
245
+ dir: "log",
246
+ dirxml: "log",
247
+ error: "error",
248
+ group: "log",
249
+ groupCollapsed: "log",
250
+ groupEnd: "log",
251
+ info: "info",
252
+ log: "log",
253
+ profile: "log",
254
+ profileEnd: "log",
255
+ table: "log",
256
+ time: "log",
257
+ timeEnd: "log",
258
+ timeLog: "log",
259
+ timeStamp: "log",
260
+ trace: "log",
261
+ warn: "warn"
262
+ };
263
+ var _idCounter = 0;
264
+ function nextId() {
265
+ _idCounter += 1;
266
+ return Date.now() + "-" + _idCounter;
267
+ }
268
+ var state = {
269
+ installed: false,
270
+ sessionId: "",
271
+ queue: [],
272
+ flushTimer: null,
273
+ counters: {},
274
+ timers: {},
275
+ groupDepth: 0,
276
+ originalMethods: {}
277
+ };
278
+ function handleMethodSemantics(method, args) {
279
+ switch (method) {
280
+ case "assert": {
281
+ var condition = !!args[0];
282
+ return { assertionPassed: condition };
283
+ }
284
+ case "count": {
285
+ var countLabel = String(args[0] !== void 0 ? args[0] : "default");
286
+ state.counters[countLabel] = (state.counters[countLabel] || 0) + 1;
287
+ return { counterValue: state.counters[countLabel] };
288
+ }
289
+ case "countReset": {
290
+ var resetLabel = String(args[0] !== void 0 ? args[0] : "default");
291
+ state.counters[resetLabel] = 0;
292
+ return { counterValue: 0 };
293
+ }
294
+ case "time": {
295
+ var timeLabel = String(args[0] !== void 0 ? args[0] : "default");
296
+ state.timers[timeLabel] = Date.now();
297
+ return { timerLabel: timeLabel };
298
+ }
299
+ case "timeLog":
300
+ case "timeEnd": {
301
+ var teLabel = String(args[0] !== void 0 ? args[0] : "default");
302
+ var start = state.timers[teLabel];
303
+ var elapsed = start !== void 0 ? Date.now() - start : -1;
304
+ if (method === "timeEnd") {
305
+ delete state.timers[teLabel];
306
+ }
307
+ return { timerLabel: teLabel, timerElapsed: elapsed };
308
+ }
309
+ case "timeStamp": {
310
+ var tsLabel = String(args[0] !== void 0 ? args[0] : "");
311
+ return { timerLabel: tsLabel };
312
+ }
313
+ case "group":
314
+ case "groupCollapsed": {
315
+ var depth = state.groupDepth;
316
+ state.groupDepth += 1;
317
+ return { groupDepth: depth };
318
+ }
319
+ case "groupEnd": {
320
+ if (state.groupDepth > 0) state.groupDepth -= 1;
321
+ return { groupDepth: state.groupDepth };
322
+ }
323
+ default:
324
+ return {};
325
+ }
326
+ }
327
+ function createWrapper(method, config) {
328
+ var maxLen = config.maxArgLength !== void 0 ? config.maxArgLength : 2e3;
329
+ var passthrough = config.passthrough !== false;
330
+ return function(...args) {
331
+ if (passthrough) {
332
+ var native = state.originalMethods[method];
333
+ if (typeof native === "function") {
334
+ try {
335
+ native.apply(console, args);
336
+ } catch (_) {
337
+ }
338
+ }
339
+ }
340
+ if (method === "assert" && args[0]) return;
341
+ var semantics = handleMethodSemantics(method, args);
342
+ var now = Date.now();
343
+ var serialisedArgs = args.map(function(a) {
344
+ return serializeArg(a, maxLen);
345
+ });
346
+ var entry = Object.assign(
347
+ {
348
+ id: nextId(),
349
+ method,
350
+ level: METHOD_TO_LEVEL[method],
351
+ timestamp: new Date(now).toISOString(),
352
+ timestampMs: now,
353
+ url: typeof location !== "undefined" ? location.href : "",
354
+ userAgent: typeof navigator !== "undefined" ? navigator.userAgent : "",
355
+ sessionId: state.sessionId,
356
+ args: serialisedArgs,
357
+ stack: method === "trace" || method === "error" ? captureStack(1) : void 0,
358
+ groupDepth: state.groupDepth
359
+ },
360
+ semantics
361
+ );
362
+ state.queue.push(entry);
363
+ var maxQueue = config.maxQueueSize !== void 0 ? config.maxQueueSize : 50;
364
+ if (state.queue.length >= maxQueue) {
365
+ flushNow(config);
366
+ }
367
+ };
368
+ }
369
+ function flushNow(config) {
370
+ if (state.queue.length === 0) return;
371
+ var batch = state.queue.splice(0);
372
+ sendBatch(batch, config);
373
+ }
374
+ function scheduleFlush(config) {
375
+ if (state.flushTimer !== null) return;
376
+ var interval = config.flushInterval !== void 0 ? config.flushInterval : 2e3;
377
+ state.flushTimer = setTimeout(function() {
378
+ state.flushTimer = null;
379
+ flushNow(config);
380
+ scheduleFlush(config);
381
+ }, interval);
382
+ }
383
+ function installUnloadFlush(config) {
384
+ var endpoint = config.endpoint;
385
+ if (!endpoint) return;
386
+ var handler = function() {
387
+ if (state.queue.length === 0) return;
388
+ var batch = state.queue.splice(0);
389
+ if (!sendBeaconFallback(batch, endpoint)) {
390
+ try {
391
+ var xhr = new XMLHttpRequest();
392
+ xhr.open(
393
+ "POST",
394
+ endpoint,
395
+ false
396
+ /* sync */
397
+ );
398
+ xhr.setRequestHeader("Content-Type", "application/json");
399
+ xhr.send(JSON.stringify({ logs: batch }));
400
+ } catch (_) {
401
+ }
402
+ }
403
+ };
404
+ if (typeof addEventListener !== "undefined") {
405
+ addEventListener("visibilitychange", function() {
406
+ if (document.visibilityState === "hidden") handler();
407
+ });
408
+ addEventListener("pagehide", handler);
409
+ addEventListener("beforeunload", handler);
410
+ }
411
+ }
412
+ function install(userConfig) {
413
+ if (state.installed) return;
414
+ var config = Object.assign(
415
+ {
416
+ endpoint: "/api/console-logs",
417
+ methods: ALL_METHODS,
418
+ passthrough: true,
419
+ flushInterval: 2e3,
420
+ maxQueueSize: 50,
421
+ sessionKey: "__cpoly_sid",
422
+ storageType: "localStorage",
423
+ maxArgLength: 2e3
424
+ },
425
+ userConfig || {}
426
+ );
427
+ state.sessionId = resolveSessionId(config);
428
+ var methodsToWrap = config.methods || ALL_METHODS;
429
+ for (var i = 0; i < methodsToWrap.length; i++) {
430
+ var method = methodsToWrap[i];
431
+ var original = console[method];
432
+ if (typeof original === "function") {
433
+ state.originalMethods[method] = original;
434
+ } else {
435
+ state.originalMethods[method] = function() {
436
+ };
437
+ }
438
+ console[method] = createWrapper(method, config);
439
+ }
440
+ scheduleFlush(config);
441
+ installUnloadFlush(config);
442
+ state.installed = true;
443
+ }
444
+ function uninstall(config) {
445
+ if (!state.installed) return;
446
+ if (config) flushNow(config);
447
+ for (var method in state.originalMethods) {
448
+ if (Object.prototype.hasOwnProperty.call(state.originalMethods, method)) {
449
+ var orig = state.originalMethods[method];
450
+ if (orig !== void 0) {
451
+ console[method] = orig;
452
+ }
453
+ }
454
+ }
455
+ state.originalMethods = {};
456
+ state.installed = false;
457
+ if (state.flushTimer !== null) {
458
+ clearTimeout(state.flushTimer);
459
+ state.flushTimer = null;
460
+ }
461
+ }
462
+ function flush(config) {
463
+ flushNow(config);
464
+ }
465
+ function getQueue() {
466
+ return state.queue.slice();
467
+ }
468
+ function isInstalled() {
469
+ return state.installed;
470
+ }
471
+
472
+ // index.ts
473
+ (function autoInstall() {
474
+ if (typeof document === "undefined") return;
475
+ var scripts = document.querySelectorAll("script[data-endpoint]");
476
+ if (!scripts || scripts.length === 0) return;
477
+ var tag = scripts[scripts.length - 1];
478
+ var endpoint = tag.getAttribute("data-endpoint");
479
+ if (!endpoint) return;
480
+ var rawMethods = tag.getAttribute("data-methods");
481
+ var storage = tag.getAttribute("data-storage");
482
+ var flushRaw = tag.getAttribute("data-flush-interval");
483
+ var maxQueueRaw = tag.getAttribute("data-max-queue");
484
+ var maxArgRaw = tag.getAttribute("data-max-arg-length");
485
+ var config = { endpoint };
486
+ if (rawMethods) {
487
+ config.methods = rawMethods.split(",").map(function(m) {
488
+ return m.trim();
489
+ });
490
+ }
491
+ if (storage) config.storageType = storage;
492
+ if (flushRaw) config.flushInterval = parseInt(flushRaw, 10);
493
+ if (maxQueueRaw) config.maxQueueSize = parseInt(maxQueueRaw, 10);
494
+ if (maxArgRaw) config.maxArgLength = parseInt(maxArgRaw, 10);
495
+ install(config);
496
+ })();
497
+ })();