luchy 0.0.1 → 0.2.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/README.md +373 -0
- package/dist/api/index.d.ts +112 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +125 -0
- package/dist/api/schema.d.ts +457 -0
- package/dist/client.d.ts +23 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +314 -0
- package/dist/react-router.d.ts +164 -0
- package/dist/react-router.d.ts.map +1 -0
- package/dist/react-router.js +107 -0
- package/dist/script/luchy.js +357 -0
- package/dist/script/luchy.js.br +0 -0
- package/dist/script/luchy.js.gz +0 -0
- package/dist/script/luchy.min.js +357 -0
- package/dist/script/luchy.min.js.br +0 -0
- package/dist/script/luchy.min.js.gz +0 -0
- package/dist/server.d.ts +71 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +40 -0
- package/package.json +69 -9
- package/index.js +0 -1
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
3
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
4
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
5
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
6
|
+
var __spreadValues = (a, b) => {
|
|
7
|
+
for (var prop in b || (b = {}))
|
|
8
|
+
if (__hasOwnProp.call(b, prop))
|
|
9
|
+
__defNormalProp(a, prop, b[prop]);
|
|
10
|
+
if (__getOwnPropSymbols)
|
|
11
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
12
|
+
if (__propIsEnum.call(b, prop))
|
|
13
|
+
__defNormalProp(a, prop, b[prop]);
|
|
14
|
+
}
|
|
15
|
+
return a;
|
|
16
|
+
};
|
|
17
|
+
var __async = (__this, __arguments, generator) => {
|
|
18
|
+
return new Promise((resolve, reject) => {
|
|
19
|
+
var fulfilled = (value) => {
|
|
20
|
+
try {
|
|
21
|
+
step(generator.next(value));
|
|
22
|
+
} catch (e) {
|
|
23
|
+
reject(e);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
var rejected = (value) => {
|
|
27
|
+
try {
|
|
28
|
+
step(generator.throw(value));
|
|
29
|
+
} catch (e) {
|
|
30
|
+
reject(e);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
|
|
34
|
+
step((generator = generator.apply(__this, __arguments)).next());
|
|
35
|
+
});
|
|
36
|
+
};
|
|
37
|
+
(function(exports) {
|
|
38
|
+
"use strict";
|
|
39
|
+
function sendEvent(options) {
|
|
40
|
+
return __async(this, null, function* () {
|
|
41
|
+
const { endpoint, apiKey, data, onError } = options;
|
|
42
|
+
try {
|
|
43
|
+
yield fetch(endpoint, {
|
|
44
|
+
method: "POST",
|
|
45
|
+
headers: {
|
|
46
|
+
"Content-Type": "application/json",
|
|
47
|
+
Authorization: `Bearer ${apiKey}`
|
|
48
|
+
},
|
|
49
|
+
body: JSON.stringify(data),
|
|
50
|
+
keepalive: true
|
|
51
|
+
// Important for page unload events
|
|
52
|
+
});
|
|
53
|
+
} catch (error) {
|
|
54
|
+
onError == null ? void 0 : onError(error);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
function collectBrowserData() {
|
|
59
|
+
var _a;
|
|
60
|
+
return {
|
|
61
|
+
userAgent: navigator.userAgent,
|
|
62
|
+
language: navigator.language || ((_a = navigator.languages) == null ? void 0 : _a[0]) || "en"
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function luchy(options) {
|
|
66
|
+
const endpoint = options.endpoint || "https://api.luchy.app/ingest";
|
|
67
|
+
const { apiKey, trackLocalhost = false } = options;
|
|
68
|
+
const shouldTrack = () => {
|
|
69
|
+
if (trackLocalhost) return true;
|
|
70
|
+
return location.hostname !== "localhost" && location.hostname !== "127.0.0.1";
|
|
71
|
+
};
|
|
72
|
+
function trackPageview(data) {
|
|
73
|
+
return __async(this, null, function* () {
|
|
74
|
+
if (!shouldTrack()) return;
|
|
75
|
+
const browserData = collectBrowserData();
|
|
76
|
+
const pathname = (data == null ? void 0 : data.pathname) || location.pathname;
|
|
77
|
+
const referrer = (data == null ? void 0 : data.referrer) || document.referrer || void 0;
|
|
78
|
+
yield sendEvent({
|
|
79
|
+
endpoint: `${endpoint}/pageview`,
|
|
80
|
+
apiKey,
|
|
81
|
+
data: __spreadValues({
|
|
82
|
+
pathname,
|
|
83
|
+
referrer
|
|
84
|
+
}, browserData)
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
function trackEvent(name, payload, options2) {
|
|
89
|
+
return __async(this, null, function* () {
|
|
90
|
+
if (!shouldTrack()) return;
|
|
91
|
+
const browserData = collectBrowserData();
|
|
92
|
+
const pathname = location.pathname;
|
|
93
|
+
yield sendEvent({
|
|
94
|
+
endpoint: `${endpoint}/event`,
|
|
95
|
+
apiKey,
|
|
96
|
+
data: __spreadValues({
|
|
97
|
+
name,
|
|
98
|
+
type: options2 == null ? void 0 : options2.type,
|
|
99
|
+
pathname,
|
|
100
|
+
// `payload`, NOT `props`. The ingest endpoint validates with a
|
|
101
|
+
// non-passthrough zod object, so an unknown key is stripped and the
|
|
102
|
+
// event is stored with no properties at all — 200, success, no data.
|
|
103
|
+
payload
|
|
104
|
+
}, browserData)
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
let autoPageviewsEnabled = false;
|
|
109
|
+
let autoOutboundEnabled = false;
|
|
110
|
+
let autoEventsEnabled = false;
|
|
111
|
+
let hashRoutingEnabled = false;
|
|
112
|
+
let pageEnteredAt = 0;
|
|
113
|
+
let currentPathname = "";
|
|
114
|
+
let pageleaveSent = false;
|
|
115
|
+
let trackedLocation = "";
|
|
116
|
+
function locationKey() {
|
|
117
|
+
return `${location.pathname}${location.search || ""}${location.hash || ""}`;
|
|
118
|
+
}
|
|
119
|
+
function trackNavigation() {
|
|
120
|
+
const next = locationKey();
|
|
121
|
+
if (next === trackedLocation) return;
|
|
122
|
+
sendPageleave();
|
|
123
|
+
trackedLocation = next;
|
|
124
|
+
trackPageview();
|
|
125
|
+
resetPageTiming();
|
|
126
|
+
}
|
|
127
|
+
function sendPageleave() {
|
|
128
|
+
if (pageleaveSent || !currentPathname || !pageEnteredAt) return;
|
|
129
|
+
pageleaveSent = true;
|
|
130
|
+
const duration = Date.now() - pageEnteredAt;
|
|
131
|
+
trackEvent(
|
|
132
|
+
"__pageleave",
|
|
133
|
+
{ duration, pathname: currentPathname },
|
|
134
|
+
{ type: "__internal" }
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
function resetPageTiming() {
|
|
138
|
+
pageEnteredAt = Date.now();
|
|
139
|
+
currentPathname = location.pathname;
|
|
140
|
+
pageleaveSent = false;
|
|
141
|
+
}
|
|
142
|
+
let visibilityListener = null;
|
|
143
|
+
let pagehideListener = null;
|
|
144
|
+
let originalPushState = null;
|
|
145
|
+
let originalReplaceState = null;
|
|
146
|
+
let popstateListener = null;
|
|
147
|
+
let hashchangeListener = null;
|
|
148
|
+
let clickListener = null;
|
|
149
|
+
let dataEventClickListener = null;
|
|
150
|
+
function enableAutoPageviews() {
|
|
151
|
+
if (autoPageviewsEnabled) return;
|
|
152
|
+
autoPageviewsEnabled = true;
|
|
153
|
+
trackedLocation = locationKey();
|
|
154
|
+
trackPageview();
|
|
155
|
+
resetPageTiming();
|
|
156
|
+
if (!originalPushState) {
|
|
157
|
+
originalPushState = history.pushState.bind(history);
|
|
158
|
+
}
|
|
159
|
+
if (!originalReplaceState) {
|
|
160
|
+
originalReplaceState = history.replaceState.bind(history);
|
|
161
|
+
}
|
|
162
|
+
history.pushState = function(data, unused, url) {
|
|
163
|
+
originalPushState.apply(this, [data, unused, url]);
|
|
164
|
+
trackNavigation();
|
|
165
|
+
};
|
|
166
|
+
history.replaceState = function(data, unused, url) {
|
|
167
|
+
originalReplaceState.apply(this, [data, unused, url]);
|
|
168
|
+
trackNavigation();
|
|
169
|
+
};
|
|
170
|
+
popstateListener = () => {
|
|
171
|
+
trackNavigation();
|
|
172
|
+
};
|
|
173
|
+
window.addEventListener("popstate", popstateListener);
|
|
174
|
+
visibilityListener = () => {
|
|
175
|
+
if (document.visibilityState === "hidden") {
|
|
176
|
+
sendPageleave();
|
|
177
|
+
} else if (document.visibilityState === "visible") {
|
|
178
|
+
resetPageTiming();
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
document.addEventListener("visibilitychange", visibilityListener);
|
|
182
|
+
pagehideListener = () => sendPageleave();
|
|
183
|
+
window.addEventListener("pagehide", pagehideListener);
|
|
184
|
+
}
|
|
185
|
+
function disableAutoPageviews() {
|
|
186
|
+
if (!autoPageviewsEnabled) return;
|
|
187
|
+
autoPageviewsEnabled = false;
|
|
188
|
+
if (originalPushState) {
|
|
189
|
+
history.pushState = originalPushState;
|
|
190
|
+
}
|
|
191
|
+
if (originalReplaceState) {
|
|
192
|
+
history.replaceState = originalReplaceState;
|
|
193
|
+
}
|
|
194
|
+
if (popstateListener) {
|
|
195
|
+
window.removeEventListener("popstate", popstateListener);
|
|
196
|
+
popstateListener = null;
|
|
197
|
+
}
|
|
198
|
+
if (visibilityListener) {
|
|
199
|
+
document.removeEventListener("visibilitychange", visibilityListener);
|
|
200
|
+
visibilityListener = null;
|
|
201
|
+
}
|
|
202
|
+
if (pagehideListener) {
|
|
203
|
+
window.removeEventListener("pagehide", pagehideListener);
|
|
204
|
+
pagehideListener = null;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
function enableHashRouting() {
|
|
208
|
+
if (hashRoutingEnabled) return;
|
|
209
|
+
hashRoutingEnabled = true;
|
|
210
|
+
hashchangeListener = () => trackNavigation();
|
|
211
|
+
window.addEventListener("hashchange", hashchangeListener);
|
|
212
|
+
}
|
|
213
|
+
function disableHashRouting() {
|
|
214
|
+
if (!hashRoutingEnabled) return;
|
|
215
|
+
hashRoutingEnabled = false;
|
|
216
|
+
if (hashchangeListener) {
|
|
217
|
+
window.removeEventListener("hashchange", hashchangeListener);
|
|
218
|
+
hashchangeListener = null;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
function enableAutoOutboundTracking() {
|
|
222
|
+
if (autoOutboundEnabled) return;
|
|
223
|
+
autoOutboundEnabled = true;
|
|
224
|
+
clickListener = (event) => {
|
|
225
|
+
var _a;
|
|
226
|
+
const link = (_a = event.target) == null ? void 0 : _a.closest("a");
|
|
227
|
+
if (!link) return;
|
|
228
|
+
if (link.closest("[data-luchy-event]")) return;
|
|
229
|
+
const href = link.href;
|
|
230
|
+
if (!href) return;
|
|
231
|
+
try {
|
|
232
|
+
const url = new URL(href);
|
|
233
|
+
if (url.hostname !== location.hostname) {
|
|
234
|
+
trackEvent("Outbound Link", { url: href }, { type: "click" });
|
|
235
|
+
}
|
|
236
|
+
} catch (e) {
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
document.addEventListener("click", clickListener);
|
|
240
|
+
}
|
|
241
|
+
function disableAutoOutboundTracking() {
|
|
242
|
+
if (!autoOutboundEnabled) return;
|
|
243
|
+
autoOutboundEnabled = false;
|
|
244
|
+
if (clickListener) {
|
|
245
|
+
document.removeEventListener("click", clickListener);
|
|
246
|
+
clickListener = null;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
function enableAutoEvents() {
|
|
250
|
+
if (autoEventsEnabled) return;
|
|
251
|
+
autoEventsEnabled = true;
|
|
252
|
+
dataEventClickListener = (event) => {
|
|
253
|
+
var _a;
|
|
254
|
+
const target = (_a = event.target) == null ? void 0 : _a.closest(
|
|
255
|
+
"[data-luchy-event]"
|
|
256
|
+
);
|
|
257
|
+
if (!target) return;
|
|
258
|
+
const name = target.getAttribute("data-luchy-event");
|
|
259
|
+
if (!name) return;
|
|
260
|
+
const payload = {};
|
|
261
|
+
for (const attr of Array.from(target.attributes)) {
|
|
262
|
+
const prefix = attr.name.startsWith("data-luchy-payload-") ? "data-luchy-payload-" : attr.name.startsWith("data-luchy-prop-") ? "data-luchy-prop-" : null;
|
|
263
|
+
if (!prefix) continue;
|
|
264
|
+
const key = attr.name.slice(prefix.length).replace(/-/g, "_");
|
|
265
|
+
payload[key] = attr.value;
|
|
266
|
+
}
|
|
267
|
+
trackEvent(
|
|
268
|
+
name,
|
|
269
|
+
Object.keys(payload).length > 0 ? payload : void 0,
|
|
270
|
+
{ type: "click" }
|
|
271
|
+
);
|
|
272
|
+
};
|
|
273
|
+
document.addEventListener("click", dataEventClickListener);
|
|
274
|
+
}
|
|
275
|
+
function disableAutoEvents() {
|
|
276
|
+
if (!autoEventsEnabled) return;
|
|
277
|
+
autoEventsEnabled = false;
|
|
278
|
+
if (dataEventClickListener) {
|
|
279
|
+
document.removeEventListener("click", dataEventClickListener);
|
|
280
|
+
dataEventClickListener = null;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
function setOptions(options2) {
|
|
284
|
+
if (options2.autoPageviews !== void 0) {
|
|
285
|
+
if (options2.autoPageviews) {
|
|
286
|
+
enableAutoPageviews();
|
|
287
|
+
} else {
|
|
288
|
+
disableAutoPageviews();
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
if (options2.autoOutbound !== void 0) {
|
|
292
|
+
if (options2.autoOutbound) {
|
|
293
|
+
enableAutoOutboundTracking();
|
|
294
|
+
} else {
|
|
295
|
+
disableAutoOutboundTracking();
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
if (options2.autoEvents !== void 0) {
|
|
299
|
+
if (options2.autoEvents) {
|
|
300
|
+
enableAutoEvents();
|
|
301
|
+
} else {
|
|
302
|
+
disableAutoEvents();
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
if (options2.hashRouting !== void 0) {
|
|
306
|
+
if (options2.hashRouting) {
|
|
307
|
+
enableHashRouting();
|
|
308
|
+
} else {
|
|
309
|
+
disableHashRouting();
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
return {
|
|
314
|
+
trackPageview,
|
|
315
|
+
trackEvent,
|
|
316
|
+
setOptions
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
if (typeof document !== "undefined" && document.currentScript) {
|
|
320
|
+
const script = document.currentScript;
|
|
321
|
+
const apiKey = script.getAttribute("data-api-key");
|
|
322
|
+
const endpoint = script.getAttribute("data-endpoint") || void 0;
|
|
323
|
+
const trackLocalhost = script.hasAttribute("data-track-localhost");
|
|
324
|
+
if (apiKey) {
|
|
325
|
+
const tracker = luchy({
|
|
326
|
+
apiKey,
|
|
327
|
+
endpoint,
|
|
328
|
+
trackLocalhost
|
|
329
|
+
});
|
|
330
|
+
const autoPageviewsAttr = script.getAttribute("data-auto-pageviews");
|
|
331
|
+
const autoOutboundAttr = script.getAttribute("data-auto-outbound");
|
|
332
|
+
const autoEventsAttr = script.getAttribute("data-auto-events");
|
|
333
|
+
const hashRoutingAttr = script.getAttribute("data-hash-routing");
|
|
334
|
+
const options = {};
|
|
335
|
+
if (autoPageviewsAttr !== null) {
|
|
336
|
+
options.autoPageviews = autoPageviewsAttr !== "false";
|
|
337
|
+
}
|
|
338
|
+
if (autoOutboundAttr !== null) {
|
|
339
|
+
options.autoOutbound = autoOutboundAttr !== "false";
|
|
340
|
+
}
|
|
341
|
+
if (autoEventsAttr !== null) {
|
|
342
|
+
options.autoEvents = autoEventsAttr !== "false";
|
|
343
|
+
}
|
|
344
|
+
if (hashRoutingAttr !== null) {
|
|
345
|
+
options.hashRouting = hashRoutingAttr !== "false";
|
|
346
|
+
}
|
|
347
|
+
tracker.setOptions(__spreadValues({
|
|
348
|
+
autoPageviews: true,
|
|
349
|
+
autoOutbound: true,
|
|
350
|
+
autoEvents: true,
|
|
351
|
+
hashRouting: true
|
|
352
|
+
}, options));
|
|
353
|
+
window.luchy = tracker;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
exports.luchy = luchy;
|
|
357
|
+
})(this.luchy = this.luchy || {});
|
|
Binary file
|
|
Binary file
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-side tracking.
|
|
3
|
+
*
|
|
4
|
+
* The browser tracker (`./index.ts`) can only see what happens in a page. A
|
|
5
|
+
* server sees what the application actually *did* — an order placed, a payment
|
|
6
|
+
* recorded, a webhook processed — and those are usually the events worth
|
|
7
|
+
* having. This entry point carries no browser globals (`window`, `document`,
|
|
8
|
+
* `location`, `navigator`), so it runs in Node, Bun, Deno and Cloudflare
|
|
9
|
+
* Workers.
|
|
10
|
+
*
|
|
11
|
+
* It exists mostly so that callers stop hand-rolling `fetch` against the
|
|
12
|
+
* ingest API: doing that is how the `props`/`payload` field-name split shipped
|
|
13
|
+
* unnoticed, since nothing typed the wire format for a non-browser sender.
|
|
14
|
+
*
|
|
15
|
+
* The transport now lives in `./api`, generated from the API's own OpenAPI
|
|
16
|
+
* document, so the wire format is no longer restated here at all. This module
|
|
17
|
+
* is the ergonomic face of it: two methods, no client object to hold.
|
|
18
|
+
*/
|
|
19
|
+
export type LuchyServerOptions = {
|
|
20
|
+
apiKey: string;
|
|
21
|
+
/** Defaults to the hosted ingest API. */
|
|
22
|
+
endpoint?: string;
|
|
23
|
+
/**
|
|
24
|
+
* Called when an event fails to reach the ingest API. Analytics must never
|
|
25
|
+
* break the request it is describing, so failures are swallowed by default —
|
|
26
|
+
* pass this to log them.
|
|
27
|
+
*/
|
|
28
|
+
onError?: (error: unknown) => void;
|
|
29
|
+
};
|
|
30
|
+
/** A property bag. Nested objects are not supported by the ingest API. */
|
|
31
|
+
export type LuchyPayload = Record<string, string | number | boolean>;
|
|
32
|
+
export type LuchyServerEvent = {
|
|
33
|
+
/** The event name, e.g. `order:placed`. */
|
|
34
|
+
name: string;
|
|
35
|
+
/** Where it happened — a route, not a raw URL with ids in it. */
|
|
36
|
+
pathname: string;
|
|
37
|
+
/** Free-form category, e.g. `server`, `webhook`, `cron`. */
|
|
38
|
+
type?: string;
|
|
39
|
+
payload?: LuchyPayload;
|
|
40
|
+
/** Forward the end user's UA when the event was caused by a request. */
|
|
41
|
+
userAgent?: string;
|
|
42
|
+
language?: string;
|
|
43
|
+
/** ISO country code. Cloudflare deployments can leave this to the server. */
|
|
44
|
+
country?: string;
|
|
45
|
+
};
|
|
46
|
+
export type LuchyServerPageview = {
|
|
47
|
+
pathname: string;
|
|
48
|
+
referrer?: string;
|
|
49
|
+
userAgent?: string;
|
|
50
|
+
language?: string;
|
|
51
|
+
country?: string;
|
|
52
|
+
};
|
|
53
|
+
export type LuchyServerTracker = {
|
|
54
|
+
trackEvent(event: LuchyServerEvent): Promise<void>;
|
|
55
|
+
trackPageview(pageview: LuchyServerPageview): Promise<void>;
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Creates a server-side tracker. Every method resolves rather than rejects —
|
|
59
|
+
* see `onError`.
|
|
60
|
+
*
|
|
61
|
+
* ```ts
|
|
62
|
+
* const luchy = createServerTracker({ apiKey: process.env.LUCHY_API_KEY! });
|
|
63
|
+
* await luchy.trackEvent({
|
|
64
|
+
* name: 'order:placed',
|
|
65
|
+
* pathname: '/checkout',
|
|
66
|
+
* payload: { plan: 'pro', amount: 29 }
|
|
67
|
+
* });
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
export declare function createServerTracker(options: LuchyServerOptions): LuchyServerTracker;
|
|
71
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAMA;;;;;;;;;;;;;;;;;GAiBG;AAEH,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,yCAAyC;IACzC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CACpC,CAAC;AAEF,0EAA0E;AAC1E,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC;AAErE,MAAM,MAAM,gBAAgB,GAAG;IAC7B,2CAA2C;IAC3C,IAAI,EAAE,MAAM,CAAC;IACb,iEAAiE;IACjE,QAAQ,EAAE,MAAM,CAAC;IACjB,4DAA4D;IAC5D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,wEAAwE;IACxE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,6EAA6E;IAC7E,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,UAAU,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnD,aAAa,CAAC,QAAQ,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7D,CAAC;AAcF;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,kBAAkB,GAC1B,kBAAkB,CAkCpB"}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { createLuchyClient } from "./api/index.js";
|
|
2
|
+
const DEFAULT_ENDPOINT = "https://api.luchy.app/ingest";
|
|
3
|
+
function apiBaseFrom(endpoint) {
|
|
4
|
+
const withoutSlash = endpoint.replace(/\/+$/, "");
|
|
5
|
+
return withoutSlash.replace(/\/ingest$/, "");
|
|
6
|
+
}
|
|
7
|
+
function createServerTracker(options) {
|
|
8
|
+
const client = createLuchyClient({
|
|
9
|
+
apiKey: options.apiKey,
|
|
10
|
+
endpoint: apiBaseFrom(options.endpoint || DEFAULT_ENDPOINT),
|
|
11
|
+
onError: options.onError
|
|
12
|
+
});
|
|
13
|
+
return {
|
|
14
|
+
async trackEvent(event) {
|
|
15
|
+
const body = {
|
|
16
|
+
name: event.name,
|
|
17
|
+
type: event.type,
|
|
18
|
+
pathname: event.pathname,
|
|
19
|
+
payload: event.payload,
|
|
20
|
+
userAgent: event.userAgent,
|
|
21
|
+
language: event.language,
|
|
22
|
+
country: event.country
|
|
23
|
+
};
|
|
24
|
+
await client.trackEvent(body);
|
|
25
|
+
},
|
|
26
|
+
async trackPageview(pageview) {
|
|
27
|
+
const body = {
|
|
28
|
+
pathname: pageview.pathname,
|
|
29
|
+
referrer: pageview.referrer,
|
|
30
|
+
userAgent: pageview.userAgent,
|
|
31
|
+
language: pageview.language,
|
|
32
|
+
country: pageview.country
|
|
33
|
+
};
|
|
34
|
+
await client.trackPageview(body);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
export {
|
|
39
|
+
createServerTracker
|
|
40
|
+
};
|
package/package.json
CHANGED
|
@@ -1,12 +1,72 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "luchy",
|
|
3
|
-
"version": "0.0
|
|
4
|
-
"description": "",
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Luchy tracking - browser script + server-side tracker",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./server": {
|
|
14
|
+
"types": "./dist/server.d.ts",
|
|
15
|
+
"import": "./dist/server.js"
|
|
16
|
+
},
|
|
17
|
+
"./react-router": {
|
|
18
|
+
"types": "./dist/react-router.d.ts",
|
|
19
|
+
"import": "./dist/react-router.js"
|
|
20
|
+
},
|
|
21
|
+
"./api": {
|
|
22
|
+
"types": "./dist/api/index.d.ts",
|
|
23
|
+
"import": "./dist/api/index.js"
|
|
24
|
+
},
|
|
25
|
+
"./package.json": "./package.json"
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"dist"
|
|
29
|
+
],
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "vite build && vite build --minify false && bunx tsx build.ts && bun run build:lib",
|
|
32
|
+
"upload": "bunx tsx upload.ts",
|
|
33
|
+
"deploy": "bun run build && bun run upload",
|
|
34
|
+
"test": "vitest run",
|
|
35
|
+
"test:watch": "vitest",
|
|
36
|
+
"build:lib": "vite build --config vite.lib.config.ts",
|
|
37
|
+
"generate": "openapi-typescript openapi.json -o src/api/schema.d.ts"
|
|
38
|
+
},
|
|
39
|
+
"keywords": [
|
|
40
|
+
"analytics",
|
|
41
|
+
"tracking",
|
|
42
|
+
"luchy",
|
|
43
|
+
"pageview",
|
|
44
|
+
"events"
|
|
45
|
+
],
|
|
46
|
+
"author": "Willy Ovalle",
|
|
47
|
+
"license": "MIT",
|
|
48
|
+
"repository": {
|
|
49
|
+
"type": "git",
|
|
50
|
+
"url": "git+https://github.com/luchyio/luchy.git",
|
|
51
|
+
"directory": "packages/tracker"
|
|
52
|
+
},
|
|
53
|
+
"publishConfig": {
|
|
54
|
+
"access": "public"
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@types/node": "^24.7.0",
|
|
58
|
+
"jsdom": "^25.0.1",
|
|
59
|
+
"openapi-typescript": "^7.13.0",
|
|
60
|
+
"terser": "^5.37.0",
|
|
61
|
+
"tsx": "^4.20.6",
|
|
62
|
+
"typescript": "^5.9.3",
|
|
63
|
+
"vite": "^6.0.3",
|
|
64
|
+
"vite-plugin-compression": "^0.5.1",
|
|
65
|
+
"vite-plugin-dts": "^4.3.0",
|
|
66
|
+
"vitest": "^2.1.8",
|
|
67
|
+
"wrangler": "^4.43.0"
|
|
68
|
+
},
|
|
69
|
+
"dependencies": {
|
|
70
|
+
"openapi-fetch": "^0.17.0"
|
|
71
|
+
}
|
|
12
72
|
}
|
package/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export const check = "soon";
|