@doow/track-nextjs 0.1.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) 2024 Doow
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,145 @@
1
+ # Doow Track Next.js SDK
2
+
3
+ [![Next.js](https://img.shields.io/badge/Next.js-13+-black)](https://nextjs.org/)
4
+ [![License](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
5
+
6
+ Official Next.js SDK for [Doow](https://doow.co) usage telemetry with client and server support.
7
+
8
+ ## Features
9
+
10
+ | Feature | Description |
11
+ |---------|-------------|
12
+ | **App Router** | Full support for Next.js 13+ App Router |
13
+ | **Server Actions** | Track from server components and actions |
14
+ | **Client Components** | React hooks with batching and compression |
15
+ | **Edge Runtime** | Works in Edge and Node.js runtimes |
16
+
17
+ ---
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ npm install @doow/track-nextjs
23
+ ```
24
+
25
+ ---
26
+
27
+ ## Client-Side Usage
28
+
29
+ ### Provider Setup (layout.tsx)
30
+
31
+ ```tsx
32
+ import { DoowProvider } from '@doow/track-nextjs';
33
+
34
+ export default function RootLayout({ children }) {
35
+ return (
36
+ <html>
37
+ <body>
38
+ <DoowProvider apiKey={process.env.NEXT_PUBLIC_DOOW_API_KEY!}>
39
+ {children}
40
+ </DoowProvider>
41
+ </body>
42
+ </html>
43
+ );
44
+ }
45
+ ```
46
+
47
+ ### Track in Client Components
48
+
49
+ ```tsx
50
+ 'use client';
51
+
52
+ import { useTrackEvent } from '@doow/track-nextjs';
53
+
54
+ export function FeatureButton() {
55
+ const track = useTrackEvent();
56
+
57
+ return (
58
+ <button onClick={() => track({
59
+ metric: 'feature_usage',
60
+ quantity: 1,
61
+ licenseId: 'lic_abc123',
62
+ })}>
63
+ Use Feature
64
+ </button>
65
+ );
66
+ }
67
+ ```
68
+
69
+ ---
70
+
71
+ ## Server-Side Usage
72
+
73
+ ### Initialize (once)
74
+
75
+ ```ts
76
+ // lib/doow.ts
77
+ import { initServerTracker } from '@doow/track-nextjs/server';
78
+
79
+ export const tracker = initServerTracker(process.env.DOOW_API_KEY!);
80
+ ```
81
+
82
+ ### Server Actions
83
+
84
+ ```ts
85
+ 'use server';
86
+
87
+ import { trackServerEvent } from '@doow/track-nextjs/server';
88
+
89
+ export async function generateReport(formData: FormData) {
90
+ await trackServerEvent({
91
+ metric: 'report_generated',
92
+ quantity: 1,
93
+ licenseId: getLicenseId(),
94
+ });
95
+
96
+ return generateReportData(formData);
97
+ }
98
+ ```
99
+
100
+ ### Route Handlers
101
+
102
+ ```ts
103
+ // app/api/process/route.ts
104
+ import { getServerTracker } from '@doow/track-nextjs/server';
105
+
106
+ export async function POST(request: Request) {
107
+ const tracker = getServerTracker();
108
+
109
+ await tracker.track({
110
+ metric: 'api_call',
111
+ quantity: 1,
112
+ licenseId: getLicenseId(request),
113
+ });
114
+
115
+ return Response.json({ success: true });
116
+ }
117
+ ```
118
+
119
+ ---
120
+
121
+ ## Middleware Tracking
122
+
123
+ ```ts
124
+ // middleware.ts
125
+ import { ServerTracker } from '@doow/track-nextjs/server';
126
+
127
+ const tracker = new ServerTracker(process.env.DOOW_API_KEY!);
128
+
129
+ export async function middleware(request: NextRequest) {
130
+ await tracker.track({
131
+ metric: 'page_view',
132
+ quantity: 1,
133
+ licenseId: getLicenseFromCookie(request),
134
+ attribution: { path: request.nextUrl.pathname },
135
+ });
136
+
137
+ return NextResponse.next();
138
+ }
139
+ ```
140
+
141
+ ---
142
+
143
+ ## License
144
+
145
+ MIT
@@ -0,0 +1,15 @@
1
+ import React from 'react';
2
+ import { T as TrackerOptions, D as DoowContextValue, a as TrackEvent } from './types-CTrFvUPc.mjs';
3
+ export { b as DoowProviderProps, S as ServerTrackerOptions } from './types-CTrFvUPc.mjs';
4
+
5
+ interface DoowProviderProps {
6
+ apiKey: string;
7
+ options?: TrackerOptions;
8
+ children: React.ReactNode;
9
+ }
10
+ declare function DoowProvider({ apiKey, options, children }: DoowProviderProps): JSX.Element;
11
+ declare function useDoow(): DoowContextValue;
12
+ declare function useTrackEvent(): (event: TrackEvent) => void;
13
+ declare function useTrackOnMount(event: TrackEvent): void;
14
+
15
+ export { DoowContextValue, DoowProvider, TrackEvent, TrackerOptions, useDoow, useTrackEvent, useTrackOnMount };
@@ -0,0 +1,15 @@
1
+ import React from 'react';
2
+ import { T as TrackerOptions, D as DoowContextValue, a as TrackEvent } from './types-CTrFvUPc.js';
3
+ export { b as DoowProviderProps, S as ServerTrackerOptions } from './types-CTrFvUPc.js';
4
+
5
+ interface DoowProviderProps {
6
+ apiKey: string;
7
+ options?: TrackerOptions;
8
+ children: React.ReactNode;
9
+ }
10
+ declare function DoowProvider({ apiKey, options, children }: DoowProviderProps): JSX.Element;
11
+ declare function useDoow(): DoowContextValue;
12
+ declare function useTrackEvent(): (event: TrackEvent) => void;
13
+ declare function useTrackOnMount(event: TrackEvent): void;
14
+
15
+ export { DoowContextValue, DoowProvider, TrackEvent, TrackerOptions, useDoow, useTrackEvent, useTrackOnMount };
package/dist/index.js ADDED
@@ -0,0 +1,226 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ DoowProvider: () => DoowProvider,
24
+ useDoow: () => useDoow,
25
+ useTrackEvent: () => useTrackEvent,
26
+ useTrackOnMount: () => useTrackOnMount
27
+ });
28
+ module.exports = __toCommonJS(index_exports);
29
+
30
+ // src/client.tsx
31
+ var import_react = require("react");
32
+ var import_jsx_runtime = require("react/jsx-runtime");
33
+ function generateUUID() {
34
+ if (typeof crypto !== "undefined" && crypto.randomUUID) {
35
+ return crypto.randomUUID();
36
+ }
37
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
38
+ const r = Math.random() * 16 | 0;
39
+ const v = c === "x" ? r : r & 3 | 8;
40
+ return v.toString(16);
41
+ });
42
+ }
43
+ var DEFAULT_OPTIONS = {
44
+ endpoint: "https://api.doow.co",
45
+ enabled: true,
46
+ debug: false,
47
+ flushAt: 20,
48
+ flushIntervalMs: 1e4,
49
+ maxQueueSize: 1e4,
50
+ timeoutMs: 1e4,
51
+ retryCount: 3,
52
+ disableCompression: false
53
+ };
54
+ var ClientTracker = class {
55
+ constructor(apiKey, options = {}) {
56
+ this.queue = [];
57
+ this.flushTimer = null;
58
+ this.shutdown = false;
59
+ if (!apiKey.startsWith("dk_")) {
60
+ throw new Error("API key must start with dk_");
61
+ }
62
+ this.apiKey = apiKey;
63
+ this.options = { ...DEFAULT_OPTIONS, ...options };
64
+ this.startFlushTimer();
65
+ this.setupLifecycleHooks();
66
+ }
67
+ startFlushTimer() {
68
+ if (this.flushTimer) clearInterval(this.flushTimer);
69
+ this.flushTimer = setInterval(() => this.flush(), this.options.flushIntervalMs);
70
+ }
71
+ setupLifecycleHooks() {
72
+ if (typeof window === "undefined") return;
73
+ window.addEventListener("beforeunload", () => this.flushSync());
74
+ document.addEventListener("visibilitychange", () => {
75
+ if (document.visibilityState === "hidden") this.flushSync();
76
+ });
77
+ }
78
+ log(message) {
79
+ if (this.options.debug) console.log(`[DoowTrack] ${message}`);
80
+ }
81
+ track(event) {
82
+ if (!this.options.enabled || this.shutdown) return;
83
+ if (this.queue.length >= this.options.maxQueueSize) {
84
+ this.log("Queue full, dropping event");
85
+ return;
86
+ }
87
+ const enrichedEvent = {
88
+ ...event,
89
+ timestamp: event.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
90
+ attribution: { ...event.attribution, ...this.options.attribution }
91
+ };
92
+ this.queue.push(enrichedEvent);
93
+ this.log(`Queued event: ${event.metric}`);
94
+ if (this.queue.length >= this.options.flushAt) this.flush();
95
+ }
96
+ async flush() {
97
+ if (this.queue.length === 0 || this.shutdown) return;
98
+ const batch = [...this.queue];
99
+ this.queue = [];
100
+ this.log(`Flushing ${batch.length} events`);
101
+ try {
102
+ await this.sendWithRetry(batch);
103
+ } catch (error) {
104
+ this.options.onError?.(error);
105
+ this.log(`Flush failed: ${error}`);
106
+ }
107
+ }
108
+ flushSync() {
109
+ if (this.queue.length === 0 || typeof navigator === "undefined") return;
110
+ const payload = JSON.stringify({
111
+ events: this.queue.map((e) => ({
112
+ event_id: generateUUID(),
113
+ metric: e.metric,
114
+ quantity: e.quantity,
115
+ license_id: e.licenseId,
116
+ unit: e.unit,
117
+ attribution: e.attribution,
118
+ timestamp: e.timestamp
119
+ }))
120
+ });
121
+ const blob = new Blob([payload], { type: "application/json" });
122
+ navigator.sendBeacon(`${this.options.endpoint}/telemetry/events`, blob);
123
+ this.queue = [];
124
+ }
125
+ async sendWithRetry(batch) {
126
+ const payload = JSON.stringify({
127
+ events: batch.map((e) => ({
128
+ event_id: generateUUID(),
129
+ metric: e.metric,
130
+ quantity: e.quantity,
131
+ license_id: e.licenseId,
132
+ unit: e.unit,
133
+ attribution: e.attribution,
134
+ timestamp: e.timestamp
135
+ }))
136
+ });
137
+ const headers = {
138
+ "Authorization": `Bearer ${this.apiKey}`,
139
+ "Content-Type": "application/json"
140
+ };
141
+ let body = payload;
142
+ if (!this.options.disableCompression && payload.length > 1024 && typeof CompressionStream !== "undefined") {
143
+ const stream = new Blob([payload]).stream().pipeThrough(new CompressionStream("gzip"));
144
+ body = await new Response(stream).blob();
145
+ headers["Content-Encoding"] = "gzip";
146
+ }
147
+ for (let attempt = 0; attempt <= this.options.retryCount; attempt++) {
148
+ try {
149
+ const controller = new AbortController();
150
+ const timeout = setTimeout(() => controller.abort(), this.options.timeoutMs);
151
+ const response = await fetch(`${this.options.endpoint}/telemetry/events`, {
152
+ method: "POST",
153
+ headers,
154
+ body,
155
+ signal: controller.signal
156
+ });
157
+ clearTimeout(timeout);
158
+ if (response.ok) {
159
+ this.log("Batch sent successfully");
160
+ return;
161
+ }
162
+ if (response.status === 429 || response.status >= 500) {
163
+ await this.sleep(100 * Math.pow(2, attempt));
164
+ continue;
165
+ }
166
+ throw new Error(`HTTP ${response.status}: ${await response.text()}`);
167
+ } catch (error) {
168
+ if (attempt === this.options.retryCount) throw error;
169
+ await this.sleep(100 * Math.pow(2, attempt));
170
+ }
171
+ }
172
+ }
173
+ sleep(ms) {
174
+ return new Promise((resolve) => setTimeout(resolve, ms));
175
+ }
176
+ destroy() {
177
+ this.shutdown = true;
178
+ if (this.flushTimer) clearInterval(this.flushTimer);
179
+ this.flushSync();
180
+ }
181
+ };
182
+ var DoowContext = (0, import_react.createContext)(null);
183
+ function DoowProvider({ apiKey, options, children }) {
184
+ const trackerRef = (0, import_react.useRef)(null);
185
+ if (!trackerRef.current) {
186
+ trackerRef.current = new ClientTracker(apiKey, options);
187
+ }
188
+ (0, import_react.useEffect)(() => {
189
+ return () => trackerRef.current?.destroy();
190
+ }, []);
191
+ const value = (0, import_react.useMemo)(
192
+ () => ({
193
+ track: (event) => trackerRef.current?.track(event),
194
+ flush: () => trackerRef.current?.flush() ?? Promise.resolve(),
195
+ isEnabled: options?.enabled !== false
196
+ }),
197
+ [options?.enabled]
198
+ );
199
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DoowContext.Provider, { value, children });
200
+ }
201
+ function useDoow() {
202
+ const context = (0, import_react.useContext)(DoowContext);
203
+ if (!context) throw new Error("useDoow must be used within a DoowProvider");
204
+ return context;
205
+ }
206
+ function useTrackEvent() {
207
+ const { track } = useDoow();
208
+ return track;
209
+ }
210
+ function useTrackOnMount(event) {
211
+ const { track } = useDoow();
212
+ const tracked = (0, import_react.useRef)(false);
213
+ (0, import_react.useEffect)(() => {
214
+ if (!tracked.current) {
215
+ track(event);
216
+ tracked.current = true;
217
+ }
218
+ }, []);
219
+ }
220
+ // Annotate the CommonJS export names for ESM import in node:
221
+ 0 && (module.exports = {
222
+ DoowProvider,
223
+ useDoow,
224
+ useTrackEvent,
225
+ useTrackOnMount
226
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,196 @@
1
+ // src/client.tsx
2
+ import { createContext, useContext, useEffect, useMemo, useRef } from "react";
3
+ import { jsx } from "react/jsx-runtime";
4
+ function generateUUID() {
5
+ if (typeof crypto !== "undefined" && crypto.randomUUID) {
6
+ return crypto.randomUUID();
7
+ }
8
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
9
+ const r = Math.random() * 16 | 0;
10
+ const v = c === "x" ? r : r & 3 | 8;
11
+ return v.toString(16);
12
+ });
13
+ }
14
+ var DEFAULT_OPTIONS = {
15
+ endpoint: "https://api.doow.co",
16
+ enabled: true,
17
+ debug: false,
18
+ flushAt: 20,
19
+ flushIntervalMs: 1e4,
20
+ maxQueueSize: 1e4,
21
+ timeoutMs: 1e4,
22
+ retryCount: 3,
23
+ disableCompression: false
24
+ };
25
+ var ClientTracker = class {
26
+ constructor(apiKey, options = {}) {
27
+ this.queue = [];
28
+ this.flushTimer = null;
29
+ this.shutdown = false;
30
+ if (!apiKey.startsWith("dk_")) {
31
+ throw new Error("API key must start with dk_");
32
+ }
33
+ this.apiKey = apiKey;
34
+ this.options = { ...DEFAULT_OPTIONS, ...options };
35
+ this.startFlushTimer();
36
+ this.setupLifecycleHooks();
37
+ }
38
+ startFlushTimer() {
39
+ if (this.flushTimer) clearInterval(this.flushTimer);
40
+ this.flushTimer = setInterval(() => this.flush(), this.options.flushIntervalMs);
41
+ }
42
+ setupLifecycleHooks() {
43
+ if (typeof window === "undefined") return;
44
+ window.addEventListener("beforeunload", () => this.flushSync());
45
+ document.addEventListener("visibilitychange", () => {
46
+ if (document.visibilityState === "hidden") this.flushSync();
47
+ });
48
+ }
49
+ log(message) {
50
+ if (this.options.debug) console.log(`[DoowTrack] ${message}`);
51
+ }
52
+ track(event) {
53
+ if (!this.options.enabled || this.shutdown) return;
54
+ if (this.queue.length >= this.options.maxQueueSize) {
55
+ this.log("Queue full, dropping event");
56
+ return;
57
+ }
58
+ const enrichedEvent = {
59
+ ...event,
60
+ timestamp: event.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
61
+ attribution: { ...event.attribution, ...this.options.attribution }
62
+ };
63
+ this.queue.push(enrichedEvent);
64
+ this.log(`Queued event: ${event.metric}`);
65
+ if (this.queue.length >= this.options.flushAt) this.flush();
66
+ }
67
+ async flush() {
68
+ if (this.queue.length === 0 || this.shutdown) return;
69
+ const batch = [...this.queue];
70
+ this.queue = [];
71
+ this.log(`Flushing ${batch.length} events`);
72
+ try {
73
+ await this.sendWithRetry(batch);
74
+ } catch (error) {
75
+ this.options.onError?.(error);
76
+ this.log(`Flush failed: ${error}`);
77
+ }
78
+ }
79
+ flushSync() {
80
+ if (this.queue.length === 0 || typeof navigator === "undefined") return;
81
+ const payload = JSON.stringify({
82
+ events: this.queue.map((e) => ({
83
+ event_id: generateUUID(),
84
+ metric: e.metric,
85
+ quantity: e.quantity,
86
+ license_id: e.licenseId,
87
+ unit: e.unit,
88
+ attribution: e.attribution,
89
+ timestamp: e.timestamp
90
+ }))
91
+ });
92
+ const blob = new Blob([payload], { type: "application/json" });
93
+ navigator.sendBeacon(`${this.options.endpoint}/telemetry/events`, blob);
94
+ this.queue = [];
95
+ }
96
+ async sendWithRetry(batch) {
97
+ const payload = JSON.stringify({
98
+ events: batch.map((e) => ({
99
+ event_id: generateUUID(),
100
+ metric: e.metric,
101
+ quantity: e.quantity,
102
+ license_id: e.licenseId,
103
+ unit: e.unit,
104
+ attribution: e.attribution,
105
+ timestamp: e.timestamp
106
+ }))
107
+ });
108
+ const headers = {
109
+ "Authorization": `Bearer ${this.apiKey}`,
110
+ "Content-Type": "application/json"
111
+ };
112
+ let body = payload;
113
+ if (!this.options.disableCompression && payload.length > 1024 && typeof CompressionStream !== "undefined") {
114
+ const stream = new Blob([payload]).stream().pipeThrough(new CompressionStream("gzip"));
115
+ body = await new Response(stream).blob();
116
+ headers["Content-Encoding"] = "gzip";
117
+ }
118
+ for (let attempt = 0; attempt <= this.options.retryCount; attempt++) {
119
+ try {
120
+ const controller = new AbortController();
121
+ const timeout = setTimeout(() => controller.abort(), this.options.timeoutMs);
122
+ const response = await fetch(`${this.options.endpoint}/telemetry/events`, {
123
+ method: "POST",
124
+ headers,
125
+ body,
126
+ signal: controller.signal
127
+ });
128
+ clearTimeout(timeout);
129
+ if (response.ok) {
130
+ this.log("Batch sent successfully");
131
+ return;
132
+ }
133
+ if (response.status === 429 || response.status >= 500) {
134
+ await this.sleep(100 * Math.pow(2, attempt));
135
+ continue;
136
+ }
137
+ throw new Error(`HTTP ${response.status}: ${await response.text()}`);
138
+ } catch (error) {
139
+ if (attempt === this.options.retryCount) throw error;
140
+ await this.sleep(100 * Math.pow(2, attempt));
141
+ }
142
+ }
143
+ }
144
+ sleep(ms) {
145
+ return new Promise((resolve) => setTimeout(resolve, ms));
146
+ }
147
+ destroy() {
148
+ this.shutdown = true;
149
+ if (this.flushTimer) clearInterval(this.flushTimer);
150
+ this.flushSync();
151
+ }
152
+ };
153
+ var DoowContext = createContext(null);
154
+ function DoowProvider({ apiKey, options, children }) {
155
+ const trackerRef = useRef(null);
156
+ if (!trackerRef.current) {
157
+ trackerRef.current = new ClientTracker(apiKey, options);
158
+ }
159
+ useEffect(() => {
160
+ return () => trackerRef.current?.destroy();
161
+ }, []);
162
+ const value = useMemo(
163
+ () => ({
164
+ track: (event) => trackerRef.current?.track(event),
165
+ flush: () => trackerRef.current?.flush() ?? Promise.resolve(),
166
+ isEnabled: options?.enabled !== false
167
+ }),
168
+ [options?.enabled]
169
+ );
170
+ return /* @__PURE__ */ jsx(DoowContext.Provider, { value, children });
171
+ }
172
+ function useDoow() {
173
+ const context = useContext(DoowContext);
174
+ if (!context) throw new Error("useDoow must be used within a DoowProvider");
175
+ return context;
176
+ }
177
+ function useTrackEvent() {
178
+ const { track } = useDoow();
179
+ return track;
180
+ }
181
+ function useTrackOnMount(event) {
182
+ const { track } = useDoow();
183
+ const tracked = useRef(false);
184
+ useEffect(() => {
185
+ if (!tracked.current) {
186
+ track(event);
187
+ tracked.current = true;
188
+ }
189
+ }, []);
190
+ }
191
+ export {
192
+ DoowProvider,
193
+ useDoow,
194
+ useTrackEvent,
195
+ useTrackOnMount
196
+ };
@@ -0,0 +1,16 @@
1
+ import { S as ServerTrackerOptions, a as TrackEvent } from './types-CTrFvUPc.mjs';
2
+
3
+ declare class ServerTracker {
4
+ private apiKey;
5
+ private options;
6
+ constructor(apiKey: string, options?: ServerTrackerOptions);
7
+ private log;
8
+ track(event: TrackEvent): Promise<void>;
9
+ trackBatch(events: TrackEvent[]): Promise<void>;
10
+ private sleep;
11
+ }
12
+ declare function initServerTracker(apiKey: string, options?: ServerTrackerOptions): ServerTracker;
13
+ declare function getServerTracker(): ServerTracker;
14
+ declare function trackServerEvent(event: TrackEvent): Promise<void>;
15
+
16
+ export { ServerTracker, getServerTracker, initServerTracker, trackServerEvent };
@@ -0,0 +1,16 @@
1
+ import { S as ServerTrackerOptions, a as TrackEvent } from './types-CTrFvUPc.js';
2
+
3
+ declare class ServerTracker {
4
+ private apiKey;
5
+ private options;
6
+ constructor(apiKey: string, options?: ServerTrackerOptions);
7
+ private log;
8
+ track(event: TrackEvent): Promise<void>;
9
+ trackBatch(events: TrackEvent[]): Promise<void>;
10
+ private sleep;
11
+ }
12
+ declare function initServerTracker(apiKey: string, options?: ServerTrackerOptions): ServerTracker;
13
+ declare function getServerTracker(): ServerTracker;
14
+ declare function trackServerEvent(event: TrackEvent): Promise<void>;
15
+
16
+ export { ServerTracker, getServerTracker, initServerTracker, trackServerEvent };
package/dist/server.js ADDED
@@ -0,0 +1,162 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/server.ts
21
+ var server_exports = {};
22
+ __export(server_exports, {
23
+ ServerTracker: () => ServerTracker,
24
+ getServerTracker: () => getServerTracker,
25
+ initServerTracker: () => initServerTracker,
26
+ trackServerEvent: () => trackServerEvent
27
+ });
28
+ module.exports = __toCommonJS(server_exports);
29
+ function generateUUID() {
30
+ if (typeof crypto !== "undefined" && crypto.randomUUID) {
31
+ return crypto.randomUUID();
32
+ }
33
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
34
+ const r = Math.random() * 16 | 0;
35
+ const v = c === "x" ? r : r & 3 | 8;
36
+ return v.toString(16);
37
+ });
38
+ }
39
+ var DEFAULT_OPTIONS = {
40
+ endpoint: "https://api.doow.co",
41
+ debug: false,
42
+ timeoutMs: 1e4,
43
+ retryCount: 3
44
+ };
45
+ var ServerTracker = class {
46
+ constructor(apiKey, options = {}) {
47
+ if (!apiKey.startsWith("dk_")) {
48
+ throw new Error("API key must start with dk_");
49
+ }
50
+ this.apiKey = apiKey;
51
+ this.options = { ...DEFAULT_OPTIONS, ...options };
52
+ }
53
+ log(message) {
54
+ if (this.options.debug) {
55
+ console.log(`[DoowTrack:Server] ${message}`);
56
+ }
57
+ }
58
+ async track(event) {
59
+ const enrichedEvent = {
60
+ event_id: generateUUID(),
61
+ metric: event.metric,
62
+ quantity: event.quantity,
63
+ license_id: event.licenseId,
64
+ unit: event.unit,
65
+ attribution: event.attribution,
66
+ timestamp: event.timestamp || (/* @__PURE__ */ new Date()).toISOString()
67
+ };
68
+ this.log(`Tracking: ${event.metric}`);
69
+ for (let attempt = 0; attempt <= this.options.retryCount; attempt++) {
70
+ try {
71
+ const controller = new AbortController();
72
+ const timeout = setTimeout(() => controller.abort(), this.options.timeoutMs);
73
+ const response = await fetch(`${this.options.endpoint}/telemetry/events`, {
74
+ method: "POST",
75
+ headers: {
76
+ "Authorization": `Bearer ${this.apiKey}`,
77
+ "Content-Type": "application/json"
78
+ },
79
+ body: JSON.stringify({ events: [enrichedEvent] }),
80
+ signal: controller.signal
81
+ });
82
+ clearTimeout(timeout);
83
+ if (response.ok) {
84
+ this.log("Event sent successfully");
85
+ return;
86
+ }
87
+ if (response.status === 429 || response.status >= 500) {
88
+ await this.sleep(100 * Math.pow(2, attempt));
89
+ continue;
90
+ }
91
+ throw new Error(`HTTP ${response.status}: ${await response.text()}`);
92
+ } catch (error) {
93
+ if (attempt === this.options.retryCount) throw error;
94
+ await this.sleep(100 * Math.pow(2, attempt));
95
+ }
96
+ }
97
+ }
98
+ async trackBatch(events) {
99
+ const enrichedEvents = events.map((event) => ({
100
+ event_id: generateUUID(),
101
+ metric: event.metric,
102
+ quantity: event.quantity,
103
+ license_id: event.licenseId,
104
+ unit: event.unit,
105
+ attribution: event.attribution,
106
+ timestamp: event.timestamp || (/* @__PURE__ */ new Date()).toISOString()
107
+ }));
108
+ this.log(`Tracking batch: ${events.length} events`);
109
+ for (let attempt = 0; attempt <= this.options.retryCount; attempt++) {
110
+ try {
111
+ const controller = new AbortController();
112
+ const timeout = setTimeout(() => controller.abort(), this.options.timeoutMs);
113
+ const response = await fetch(`${this.options.endpoint}/telemetry/events`, {
114
+ method: "POST",
115
+ headers: {
116
+ "Authorization": `Bearer ${this.apiKey}`,
117
+ "Content-Type": "application/json"
118
+ },
119
+ body: JSON.stringify({ events: enrichedEvents }),
120
+ signal: controller.signal
121
+ });
122
+ clearTimeout(timeout);
123
+ if (response.ok) {
124
+ this.log("Batch sent successfully");
125
+ return;
126
+ }
127
+ if (response.status === 429 || response.status >= 500) {
128
+ await this.sleep(100 * Math.pow(2, attempt));
129
+ continue;
130
+ }
131
+ throw new Error(`HTTP ${response.status}: ${await response.text()}`);
132
+ } catch (error) {
133
+ if (attempt === this.options.retryCount) throw error;
134
+ await this.sleep(100 * Math.pow(2, attempt));
135
+ }
136
+ }
137
+ }
138
+ sleep(ms) {
139
+ return new Promise((resolve) => setTimeout(resolve, ms));
140
+ }
141
+ };
142
+ var defaultTracker = null;
143
+ function initServerTracker(apiKey, options) {
144
+ defaultTracker = new ServerTracker(apiKey, options);
145
+ return defaultTracker;
146
+ }
147
+ function getServerTracker() {
148
+ if (!defaultTracker) {
149
+ throw new Error("Server tracker not initialized. Call initServerTracker() first.");
150
+ }
151
+ return defaultTracker;
152
+ }
153
+ async function trackServerEvent(event) {
154
+ return getServerTracker().track(event);
155
+ }
156
+ // Annotate the CommonJS export names for ESM import in node:
157
+ 0 && (module.exports = {
158
+ ServerTracker,
159
+ getServerTracker,
160
+ initServerTracker,
161
+ trackServerEvent
162
+ });
@@ -0,0 +1,134 @@
1
+ // src/server.ts
2
+ function generateUUID() {
3
+ if (typeof crypto !== "undefined" && crypto.randomUUID) {
4
+ return crypto.randomUUID();
5
+ }
6
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
7
+ const r = Math.random() * 16 | 0;
8
+ const v = c === "x" ? r : r & 3 | 8;
9
+ return v.toString(16);
10
+ });
11
+ }
12
+ var DEFAULT_OPTIONS = {
13
+ endpoint: "https://api.doow.co",
14
+ debug: false,
15
+ timeoutMs: 1e4,
16
+ retryCount: 3
17
+ };
18
+ var ServerTracker = class {
19
+ constructor(apiKey, options = {}) {
20
+ if (!apiKey.startsWith("dk_")) {
21
+ throw new Error("API key must start with dk_");
22
+ }
23
+ this.apiKey = apiKey;
24
+ this.options = { ...DEFAULT_OPTIONS, ...options };
25
+ }
26
+ log(message) {
27
+ if (this.options.debug) {
28
+ console.log(`[DoowTrack:Server] ${message}`);
29
+ }
30
+ }
31
+ async track(event) {
32
+ const enrichedEvent = {
33
+ event_id: generateUUID(),
34
+ metric: event.metric,
35
+ quantity: event.quantity,
36
+ license_id: event.licenseId,
37
+ unit: event.unit,
38
+ attribution: event.attribution,
39
+ timestamp: event.timestamp || (/* @__PURE__ */ new Date()).toISOString()
40
+ };
41
+ this.log(`Tracking: ${event.metric}`);
42
+ for (let attempt = 0; attempt <= this.options.retryCount; attempt++) {
43
+ try {
44
+ const controller = new AbortController();
45
+ const timeout = setTimeout(() => controller.abort(), this.options.timeoutMs);
46
+ const response = await fetch(`${this.options.endpoint}/telemetry/events`, {
47
+ method: "POST",
48
+ headers: {
49
+ "Authorization": `Bearer ${this.apiKey}`,
50
+ "Content-Type": "application/json"
51
+ },
52
+ body: JSON.stringify({ events: [enrichedEvent] }),
53
+ signal: controller.signal
54
+ });
55
+ clearTimeout(timeout);
56
+ if (response.ok) {
57
+ this.log("Event sent successfully");
58
+ return;
59
+ }
60
+ if (response.status === 429 || response.status >= 500) {
61
+ await this.sleep(100 * Math.pow(2, attempt));
62
+ continue;
63
+ }
64
+ throw new Error(`HTTP ${response.status}: ${await response.text()}`);
65
+ } catch (error) {
66
+ if (attempt === this.options.retryCount) throw error;
67
+ await this.sleep(100 * Math.pow(2, attempt));
68
+ }
69
+ }
70
+ }
71
+ async trackBatch(events) {
72
+ const enrichedEvents = events.map((event) => ({
73
+ event_id: generateUUID(),
74
+ metric: event.metric,
75
+ quantity: event.quantity,
76
+ license_id: event.licenseId,
77
+ unit: event.unit,
78
+ attribution: event.attribution,
79
+ timestamp: event.timestamp || (/* @__PURE__ */ new Date()).toISOString()
80
+ }));
81
+ this.log(`Tracking batch: ${events.length} events`);
82
+ for (let attempt = 0; attempt <= this.options.retryCount; attempt++) {
83
+ try {
84
+ const controller = new AbortController();
85
+ const timeout = setTimeout(() => controller.abort(), this.options.timeoutMs);
86
+ const response = await fetch(`${this.options.endpoint}/telemetry/events`, {
87
+ method: "POST",
88
+ headers: {
89
+ "Authorization": `Bearer ${this.apiKey}`,
90
+ "Content-Type": "application/json"
91
+ },
92
+ body: JSON.stringify({ events: enrichedEvents }),
93
+ signal: controller.signal
94
+ });
95
+ clearTimeout(timeout);
96
+ if (response.ok) {
97
+ this.log("Batch sent successfully");
98
+ return;
99
+ }
100
+ if (response.status === 429 || response.status >= 500) {
101
+ await this.sleep(100 * Math.pow(2, attempt));
102
+ continue;
103
+ }
104
+ throw new Error(`HTTP ${response.status}: ${await response.text()}`);
105
+ } catch (error) {
106
+ if (attempt === this.options.retryCount) throw error;
107
+ await this.sleep(100 * Math.pow(2, attempt));
108
+ }
109
+ }
110
+ }
111
+ sleep(ms) {
112
+ return new Promise((resolve) => setTimeout(resolve, ms));
113
+ }
114
+ };
115
+ var defaultTracker = null;
116
+ function initServerTracker(apiKey, options) {
117
+ defaultTracker = new ServerTracker(apiKey, options);
118
+ return defaultTracker;
119
+ }
120
+ function getServerTracker() {
121
+ if (!defaultTracker) {
122
+ throw new Error("Server tracker not initialized. Call initServerTracker() first.");
123
+ }
124
+ return defaultTracker;
125
+ }
126
+ async function trackServerEvent(event) {
127
+ return getServerTracker().track(event);
128
+ }
129
+ export {
130
+ ServerTracker,
131
+ getServerTracker,
132
+ initServerTracker,
133
+ trackServerEvent
134
+ };
@@ -0,0 +1,39 @@
1
+ interface TrackEvent {
2
+ metric: string;
3
+ quantity: number;
4
+ licenseId: string;
5
+ unit?: string;
6
+ attribution?: Record<string, unknown>;
7
+ timestamp?: string;
8
+ }
9
+ interface TrackerOptions {
10
+ endpoint?: string;
11
+ enabled?: boolean;
12
+ debug?: boolean;
13
+ flushAt?: number;
14
+ flushIntervalMs?: number;
15
+ maxQueueSize?: number;
16
+ timeoutMs?: number;
17
+ retryCount?: number;
18
+ disableCompression?: boolean;
19
+ attribution?: Record<string, unknown>;
20
+ onError?: (error: Error) => void;
21
+ }
22
+ interface DoowContextValue {
23
+ track: (event: TrackEvent) => void;
24
+ flush: () => Promise<void>;
25
+ isEnabled: boolean;
26
+ }
27
+ interface DoowProviderProps {
28
+ apiKey: string;
29
+ options?: TrackerOptions;
30
+ children: React.ReactNode;
31
+ }
32
+ interface ServerTrackerOptions {
33
+ endpoint?: string;
34
+ debug?: boolean;
35
+ timeoutMs?: number;
36
+ retryCount?: number;
37
+ }
38
+
39
+ export type { DoowContextValue as D, ServerTrackerOptions as S, TrackerOptions as T, TrackEvent as a, DoowProviderProps as b };
@@ -0,0 +1,39 @@
1
+ interface TrackEvent {
2
+ metric: string;
3
+ quantity: number;
4
+ licenseId: string;
5
+ unit?: string;
6
+ attribution?: Record<string, unknown>;
7
+ timestamp?: string;
8
+ }
9
+ interface TrackerOptions {
10
+ endpoint?: string;
11
+ enabled?: boolean;
12
+ debug?: boolean;
13
+ flushAt?: number;
14
+ flushIntervalMs?: number;
15
+ maxQueueSize?: number;
16
+ timeoutMs?: number;
17
+ retryCount?: number;
18
+ disableCompression?: boolean;
19
+ attribution?: Record<string, unknown>;
20
+ onError?: (error: Error) => void;
21
+ }
22
+ interface DoowContextValue {
23
+ track: (event: TrackEvent) => void;
24
+ flush: () => Promise<void>;
25
+ isEnabled: boolean;
26
+ }
27
+ interface DoowProviderProps {
28
+ apiKey: string;
29
+ options?: TrackerOptions;
30
+ children: React.ReactNode;
31
+ }
32
+ interface ServerTrackerOptions {
33
+ endpoint?: string;
34
+ debug?: boolean;
35
+ timeoutMs?: number;
36
+ retryCount?: number;
37
+ }
38
+
39
+ export type { DoowContextValue as D, ServerTrackerOptions as S, TrackerOptions as T, TrackEvent as a, DoowProviderProps as b };
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@doow/track-nextjs",
3
+ "version": "0.1.0",
4
+ "description": "Official Next.js SDK for Doow usage telemetry",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.mjs",
11
+ "require": "./dist/index.js",
12
+ "types": "./dist/index.d.ts"
13
+ },
14
+ "./server": {
15
+ "import": "./dist/server.mjs",
16
+ "require": "./dist/server.js",
17
+ "types": "./dist/server.d.ts"
18
+ }
19
+ },
20
+ "files": [
21
+ "dist"
22
+ ],
23
+ "scripts": {
24
+ "build": "tsup src/index.ts src/server.ts --format cjs,esm --dts --clean",
25
+ "dev": "tsup src/index.ts src/server.ts --format cjs,esm --dts --watch",
26
+ "lint": "eslint src",
27
+ "test": "vitest"
28
+ },
29
+ "peerDependencies": {
30
+ "next": ">=13.0.0",
31
+ "react": ">=18.0.0"
32
+ },
33
+ "devDependencies": {
34
+ "@types/react": "^18.2.0",
35
+ "next": "^14.0.0",
36
+ "react": "^18.2.0",
37
+ "tsup": "^8.0.0",
38
+ "typescript": "^5.3.0",
39
+ "vitest": "^1.0.0"
40
+ },
41
+ "keywords": [
42
+ "doow",
43
+ "telemetry",
44
+ "usage",
45
+ "billing",
46
+ "nextjs",
47
+ "react",
48
+ "server-actions"
49
+ ],
50
+ "author": "Doow",
51
+ "license": "MIT",
52
+ "repository": {
53
+ "type": "git",
54
+ "url": "https://github.com/Doow-Dev/doow-track-nextjs.git"
55
+ }
56
+ }