@wool-so/node 0.3.0 → 0.3.1
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/package.json +1 -1
- package/src/index.d.ts +140 -31
- package/src/index.js +379 -3
package/package.json
CHANGED
package/src/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const VERSION: '0.3.
|
|
1
|
+
export const VERSION: '0.3.1'
|
|
2
2
|
export const DEFAULT_ENDPOINT: 'https://wool-api.gurjas1882.workers.dev/v1/events'
|
|
3
3
|
|
|
4
4
|
export type WoolPropertyValue = string | number | boolean | null | undefined
|
|
@@ -13,9 +13,9 @@ export interface WoolFetchResponse {
|
|
|
13
13
|
export type WoolFetch = (
|
|
14
14
|
input: string,
|
|
15
15
|
init: {
|
|
16
|
-
method: 'POST'
|
|
16
|
+
method: 'GET' | 'POST'
|
|
17
17
|
headers: Record<string, string>
|
|
18
|
-
body
|
|
18
|
+
body?: string
|
|
19
19
|
}
|
|
20
20
|
) => Promise<WoolFetchResponse>
|
|
21
21
|
|
|
@@ -23,6 +23,14 @@ export interface WoolBaseOptions {
|
|
|
23
23
|
endpoint?: string
|
|
24
24
|
apiUrl?: string
|
|
25
25
|
apiHost?: string
|
|
26
|
+
apiBaseUrl?: string
|
|
27
|
+
api_base_url?: string
|
|
28
|
+
appUrl?: string
|
|
29
|
+
app_url?: string
|
|
30
|
+
dashboardUrl?: string
|
|
31
|
+
dashboard_url?: string
|
|
32
|
+
baseUrl?: string
|
|
33
|
+
base_url?: string
|
|
26
34
|
fetch?: WoolFetch
|
|
27
35
|
enabled?: boolean
|
|
28
36
|
referrer?: string
|
|
@@ -36,16 +44,6 @@ export interface WoolBaseOptions {
|
|
|
36
44
|
}
|
|
37
45
|
|
|
38
46
|
export type WoolInitOptions = WoolBaseOptions &
|
|
39
|
-
(
|
|
40
|
-
| {
|
|
41
|
-
projectId: string
|
|
42
|
-
project_id?: string
|
|
43
|
-
}
|
|
44
|
-
| {
|
|
45
|
-
projectId?: string
|
|
46
|
-
project_id: string
|
|
47
|
-
}
|
|
48
|
-
) &
|
|
49
47
|
(
|
|
50
48
|
| {
|
|
51
49
|
apiKey: string
|
|
@@ -55,24 +53,13 @@ export type WoolInitOptions = WoolBaseOptions &
|
|
|
55
53
|
apiKey?: string
|
|
56
54
|
api_key: string
|
|
57
55
|
}
|
|
58
|
-
) &
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
| {
|
|
66
|
-
siteUrl?: string
|
|
67
|
-
site_url: string
|
|
68
|
-
url?: string
|
|
69
|
-
}
|
|
70
|
-
| {
|
|
71
|
-
siteUrl?: string
|
|
72
|
-
site_url?: string
|
|
73
|
-
url: string
|
|
74
|
-
}
|
|
75
|
-
)
|
|
56
|
+
) & {
|
|
57
|
+
projectId?: string
|
|
58
|
+
project_id?: string
|
|
59
|
+
siteUrl?: string
|
|
60
|
+
site_url?: string
|
|
61
|
+
url?: string
|
|
62
|
+
}
|
|
76
63
|
|
|
77
64
|
export interface WoolIdentifyOptions {
|
|
78
65
|
groupKey?: string
|
|
@@ -109,6 +96,8 @@ export interface WoolRequestContext {
|
|
|
109
96
|
}
|
|
110
97
|
|
|
111
98
|
export interface WoolTrackOptions extends WoolRequestContextOptions {
|
|
99
|
+
projectId?: string
|
|
100
|
+
project_id?: string
|
|
112
101
|
request?: Request | WoolRequestLike
|
|
113
102
|
req?: Request | WoolRequestLike
|
|
114
103
|
source?: string
|
|
@@ -132,10 +121,91 @@ export interface WoolTrackOptions extends WoolRequestContextOptions {
|
|
|
132
121
|
}
|
|
133
122
|
|
|
134
123
|
export interface WoolPageviewOptions extends WoolRequestContextOptions {
|
|
124
|
+
projectId?: string
|
|
125
|
+
project_id?: string
|
|
135
126
|
request?: Request | WoolRequestLike
|
|
136
127
|
req?: Request | WoolRequestLike
|
|
137
128
|
}
|
|
138
129
|
|
|
130
|
+
export type WoolDatasetName =
|
|
131
|
+
| 'overview'
|
|
132
|
+
| 'performance'
|
|
133
|
+
| 'revenue'
|
|
134
|
+
| 'events'
|
|
135
|
+
| 'retention'
|
|
136
|
+
| 'goals'
|
|
137
|
+
| 'funnels'
|
|
138
|
+
| 'realtime'
|
|
139
|
+
|
|
140
|
+
export interface WoolDatasetOptions {
|
|
141
|
+
projectId?: string
|
|
142
|
+
project_id?: string
|
|
143
|
+
range?: '24h' | '3d' | '7d' | '14d' | '30d' | '3mo' | '6mo' | '12mo' | string
|
|
144
|
+
currency?: string
|
|
145
|
+
event?: string
|
|
146
|
+
eventName?: string
|
|
147
|
+
selectedEvent?: string
|
|
148
|
+
propertyKey?: string
|
|
149
|
+
propertyValue?: string
|
|
150
|
+
entity?: 'user' | 'group' | string
|
|
151
|
+
granularity?: 'daily' | 'weekly' | 'monthly' | 'day' | 'week' | 'month' | string
|
|
152
|
+
startEvent?: string
|
|
153
|
+
returnEvent?: string
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export interface WoolProject {
|
|
157
|
+
id: string
|
|
158
|
+
projectId: string
|
|
159
|
+
name: string
|
|
160
|
+
description?: string
|
|
161
|
+
siteUrl?: string
|
|
162
|
+
favicon?: string
|
|
163
|
+
createdAt?: string
|
|
164
|
+
updatedAt?: string
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export type WoolJsonObject = Record<string, unknown>
|
|
168
|
+
|
|
169
|
+
export interface WoolProjectsResponse {
|
|
170
|
+
projects: WoolProject[]
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export interface WoolProjectResponse {
|
|
174
|
+
project: WoolProject
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export type WoolDatasetResponse = WoolProjectResponse & WoolJsonObject
|
|
178
|
+
|
|
179
|
+
export interface WoolDatasetsResponse {
|
|
180
|
+
project?: WoolProject
|
|
181
|
+
overview?: WoolJsonObject
|
|
182
|
+
performance?: WoolJsonObject
|
|
183
|
+
revenue?: WoolJsonObject
|
|
184
|
+
events?: WoolJsonObject
|
|
185
|
+
retention?: WoolJsonObject
|
|
186
|
+
goals?: WoolJsonObject
|
|
187
|
+
funnels?: WoolJsonObject
|
|
188
|
+
realtime?: WoolJsonObject
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export interface WoolCard {
|
|
192
|
+
key: string
|
|
193
|
+
label: string
|
|
194
|
+
value: number | string
|
|
195
|
+
source: WoolDatasetName
|
|
196
|
+
unit?: string
|
|
197
|
+
currency?: string
|
|
198
|
+
[key: string]: unknown
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export type WoolCardsByDataset = Record<WoolDatasetName, WoolCard[]>
|
|
202
|
+
|
|
203
|
+
export interface WoolCardsResponse {
|
|
204
|
+
project?: WoolProject
|
|
205
|
+
cards: WoolCardsByDataset
|
|
206
|
+
datasets: WoolDatasetsResponse
|
|
207
|
+
}
|
|
208
|
+
|
|
139
209
|
export interface WoolResult {
|
|
140
210
|
ok: boolean
|
|
141
211
|
status: number
|
|
@@ -148,6 +218,19 @@ export interface WoolClient {
|
|
|
148
218
|
reset(): void
|
|
149
219
|
track(name: string, properties?: WoolProperties, options?: WoolTrackOptions): Promise<WoolResult>
|
|
150
220
|
pageview(options?: WoolPageviewOptions): Promise<WoolResult>
|
|
221
|
+
projects(): Promise<WoolProjectsResponse>
|
|
222
|
+
project(options?: string | WoolDatasetOptions): Promise<WoolProjectResponse>
|
|
223
|
+
dataset(name: WoolDatasetName, options?: WoolDatasetOptions): Promise<WoolDatasetResponse>
|
|
224
|
+
datasets(options?: WoolDatasetOptions): Promise<WoolDatasetsResponse>
|
|
225
|
+
cards(options?: WoolDatasetOptions): Promise<WoolCardsResponse>
|
|
226
|
+
overview(options?: WoolDatasetOptions): Promise<WoolDatasetResponse>
|
|
227
|
+
performance(options?: WoolDatasetOptions): Promise<WoolDatasetResponse>
|
|
228
|
+
revenue(options?: WoolDatasetOptions): Promise<WoolDatasetResponse>
|
|
229
|
+
events(options?: WoolDatasetOptions): Promise<WoolDatasetResponse>
|
|
230
|
+
retention(options?: WoolDatasetOptions): Promise<WoolDatasetResponse>
|
|
231
|
+
goals(options?: WoolDatasetOptions): Promise<WoolDatasetResponse>
|
|
232
|
+
funnels(options?: WoolDatasetOptions): Promise<WoolDatasetResponse>
|
|
233
|
+
realtime(options?: WoolDatasetOptions): Promise<WoolDatasetResponse>
|
|
151
234
|
requestContext(input?: Request | WoolRequestLike | string, options?: WoolRequestContextOptions): WoolRequestContext
|
|
152
235
|
}
|
|
153
236
|
|
|
@@ -163,6 +246,19 @@ export function identify(retentionId: string, options?: WoolIdentifyOptions): vo
|
|
|
163
246
|
export function reset(): void
|
|
164
247
|
export function track(name: string, properties?: WoolProperties, options?: WoolTrackOptions): Promise<WoolResult>
|
|
165
248
|
export function pageview(options?: WoolPageviewOptions): Promise<WoolResult>
|
|
249
|
+
export function projects(): Promise<WoolProjectsResponse>
|
|
250
|
+
export function project(options?: string | WoolDatasetOptions): Promise<WoolProjectResponse>
|
|
251
|
+
export function dataset(name: WoolDatasetName, options?: WoolDatasetOptions): Promise<WoolDatasetResponse>
|
|
252
|
+
export function datasets(options?: WoolDatasetOptions): Promise<WoolDatasetsResponse>
|
|
253
|
+
export function cards(options?: WoolDatasetOptions): Promise<WoolCardsResponse>
|
|
254
|
+
export function overview(options?: WoolDatasetOptions): Promise<WoolDatasetResponse>
|
|
255
|
+
export function performance(options?: WoolDatasetOptions): Promise<WoolDatasetResponse>
|
|
256
|
+
export function revenue(options?: WoolDatasetOptions): Promise<WoolDatasetResponse>
|
|
257
|
+
export function events(options?: WoolDatasetOptions): Promise<WoolDatasetResponse>
|
|
258
|
+
export function retention(options?: WoolDatasetOptions): Promise<WoolDatasetResponse>
|
|
259
|
+
export function goals(options?: WoolDatasetOptions): Promise<WoolDatasetResponse>
|
|
260
|
+
export function funnels(options?: WoolDatasetOptions): Promise<WoolDatasetResponse>
|
|
261
|
+
export function realtime(options?: WoolDatasetOptions): Promise<WoolDatasetResponse>
|
|
166
262
|
export function requestContext(
|
|
167
263
|
input?: Request | WoolRequestLike | string,
|
|
168
264
|
options?: WoolRequestContextOptions
|
|
@@ -185,6 +281,19 @@ export function honoMiddleware(options: WoolInitOptions | WoolClient): HonoMiddl
|
|
|
185
281
|
export const wool: WoolClient & {
|
|
186
282
|
init: typeof init
|
|
187
283
|
createClient: typeof createClient
|
|
284
|
+
cards: typeof cards
|
|
285
|
+
dataset: typeof dataset
|
|
286
|
+
datasets: typeof datasets
|
|
287
|
+
events: typeof events
|
|
288
|
+
funnels: typeof funnels
|
|
289
|
+
goals: typeof goals
|
|
290
|
+
overview: typeof overview
|
|
291
|
+
performance: typeof performance
|
|
292
|
+
project: typeof project
|
|
293
|
+
projects: typeof projects
|
|
294
|
+
realtime: typeof realtime
|
|
295
|
+
retention: typeof retention
|
|
296
|
+
revenue: typeof revenue
|
|
188
297
|
requestContext: typeof requestContext
|
|
189
298
|
expressMiddleware: typeof expressMiddleware
|
|
190
299
|
fastifyPlugin: typeof fastifyPlugin
|
package/src/index.js
CHANGED
|
@@ -1,9 +1,23 @@
|
|
|
1
|
-
export const VERSION = '0.3.
|
|
1
|
+
export const VERSION = '0.3.1'
|
|
2
2
|
export const DEFAULT_ENDPOINT = 'https://wool-api.gurjas1882.workers.dev/v1/events'
|
|
3
3
|
|
|
4
|
+
const DATASET_NAMES = new Set(['overview', 'performance', 'revenue', 'events', 'retention', 'goals', 'funnels', 'realtime'])
|
|
5
|
+
const DATASET_QUERY_KEYS = {
|
|
6
|
+
overview: ['range'],
|
|
7
|
+
performance: ['range'],
|
|
8
|
+
revenue: ['range', 'currency'],
|
|
9
|
+
events: ['range', 'event', 'propertyKey', 'propertyValue'],
|
|
10
|
+
retention: ['range', 'entity', 'granularity', 'startEvent', 'returnEvent'],
|
|
11
|
+
goals: ['range'],
|
|
12
|
+
funnels: ['range'],
|
|
13
|
+
realtime: []
|
|
14
|
+
}
|
|
15
|
+
|
|
4
16
|
const IDENTITY_KEY_PATTERN =
|
|
5
17
|
/^(anonymousId|anonymous_id|retentionId|retention_id|subjectKey|subject_key|groupKey|group_key)$/i
|
|
6
18
|
const TRACK_OPTION_KEYS = new Set([
|
|
19
|
+
'projectId',
|
|
20
|
+
'project_id',
|
|
7
21
|
'source',
|
|
8
22
|
'eventId',
|
|
9
23
|
'id',
|
|
@@ -58,10 +72,140 @@ function resolveEndpoint(options) {
|
|
|
58
72
|
return cleanText(options.endpoint, 2048) || endpointFromApiUrl(options.apiUrl || options.apiHost) || DEFAULT_ENDPOINT
|
|
59
73
|
}
|
|
60
74
|
|
|
75
|
+
function resolveApiBaseUrl(options) {
|
|
76
|
+
const value = cleanText(
|
|
77
|
+
options.apiBaseUrl ||
|
|
78
|
+
options.api_base_url ||
|
|
79
|
+
options.appUrl ||
|
|
80
|
+
options.app_url ||
|
|
81
|
+
options.dashboardUrl ||
|
|
82
|
+
options.dashboard_url ||
|
|
83
|
+
options.baseUrl ||
|
|
84
|
+
options.base_url,
|
|
85
|
+
2048
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
return value.replace(/\/+$/, '')
|
|
89
|
+
}
|
|
90
|
+
|
|
61
91
|
function isPlainObject(value) {
|
|
62
92
|
return Boolean(value && typeof value === 'object' && !Array.isArray(value))
|
|
63
93
|
}
|
|
64
94
|
|
|
95
|
+
function toNumber(value) {
|
|
96
|
+
const number = Number(value)
|
|
97
|
+
return Number.isFinite(number) ? number : 0
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function valueAt(source, path) {
|
|
101
|
+
return path.reduce((current, key) => (current && current[key] !== undefined ? current[key] : undefined), source)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function card(key, label, value, source, options = {}) {
|
|
105
|
+
return {
|
|
106
|
+
key,
|
|
107
|
+
label,
|
|
108
|
+
value,
|
|
109
|
+
source,
|
|
110
|
+
...options
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function topConversion(goals = []) {
|
|
115
|
+
return Math.max(...goals.map((goal) => toNumber(goal?.stats?.conversionRate)), 0)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function bestFunnelConversion(funnels = []) {
|
|
119
|
+
return Math.max(...funnels.map((funnel) => toNumber(funnel?.conversionRate)), 0)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function lowestFunnelDropoff(funnels = []) {
|
|
123
|
+
return funnels.length ? Math.min(...funnels.map((funnel) => toNumber(funnel?.dropoffRate))) : 0
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function buildCards(data) {
|
|
127
|
+
const overview = data.overview || {}
|
|
128
|
+
const realtime = data.realtime || {}
|
|
129
|
+
const revenue = data.revenue || {}
|
|
130
|
+
const retention = data.retention || {}
|
|
131
|
+
const goals = data.goals || {}
|
|
132
|
+
const funnels = data.funnels || {}
|
|
133
|
+
const events = data.events || {}
|
|
134
|
+
const performance = data.performance || {}
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
overview: [
|
|
138
|
+
card('visitors', 'Visitors', toNumber(overview.visitors), 'overview'),
|
|
139
|
+
card('visits', 'Visits', toNumber(overview.visits), 'overview'),
|
|
140
|
+
card('pageviews', 'Pageviews', toNumber(overview.pageviews), 'overview'),
|
|
141
|
+
card('bounceRate', 'Bounce rate', toNumber(overview.bounceRate), 'overview', { unit: 'percent' }),
|
|
142
|
+
card('visitDuration', 'Visit duration', toNumber(overview.visitDuration), 'overview', { unit: 'seconds' })
|
|
143
|
+
],
|
|
144
|
+
realtime: [
|
|
145
|
+
card('activeVisitors', 'Active visitors', toNumber(realtime.activeVisitors), 'realtime'),
|
|
146
|
+
card('visitors', 'Visitors', toNumber(realtime.visitors), 'realtime'),
|
|
147
|
+
card('pageviews', 'Pageviews', toNumber(realtime.pageviews), 'realtime'),
|
|
148
|
+
card('events', 'Events', toNumber(realtime.events), 'realtime')
|
|
149
|
+
],
|
|
150
|
+
revenue: [
|
|
151
|
+
card('revenue', 'Revenue', toNumber(valueAt(revenue, ['totals', 'revenue'])), 'revenue', {
|
|
152
|
+
currency: revenue.selectedCurrency || ''
|
|
153
|
+
}),
|
|
154
|
+
card('purchases', 'Purchases', toNumber(valueAt(revenue, ['totals', 'purchases'])), 'revenue'),
|
|
155
|
+
card('purchasers', 'Purchasers', toNumber(valueAt(revenue, ['totals', 'purchasers'])), 'revenue'),
|
|
156
|
+
card('averageOrderValue', 'AOV', toNumber(valueAt(revenue, ['totals', 'averageOrderValue'])), 'revenue', {
|
|
157
|
+
currency: revenue.selectedCurrency || ''
|
|
158
|
+
}),
|
|
159
|
+
card('conversionRate', 'Conversion', toNumber(valueAt(revenue, ['totals', 'conversionRate'])), 'revenue', {
|
|
160
|
+
unit: 'percent'
|
|
161
|
+
})
|
|
162
|
+
],
|
|
163
|
+
retention: [
|
|
164
|
+
card('cohorts', 'Cohorts', toNumber(valueAt(retention, ['totals', 'cohorts'])), 'retention'),
|
|
165
|
+
card('subjects', 'Subjects', toNumber(valueAt(retention, ['totals', 'subjects'])), 'retention'),
|
|
166
|
+
card('retentionRate', 'Retention', toNumber(valueAt(retention, ['totals', 'retentionRate'])), 'retention', {
|
|
167
|
+
unit: 'percent',
|
|
168
|
+
retained: toNumber(valueAt(retention, ['totals', 'retained']))
|
|
169
|
+
}),
|
|
170
|
+
card('churnRate', 'Churn', toNumber(valueAt(retention, ['totals', 'churnRate'])), 'retention', {
|
|
171
|
+
unit: 'percent',
|
|
172
|
+
churned: toNumber(valueAt(retention, ['totals', 'churned']))
|
|
173
|
+
})
|
|
174
|
+
],
|
|
175
|
+
goals: [
|
|
176
|
+
card('goals', 'Goals', Array.isArray(goals.goals) ? goals.goals.length : 0, 'goals'),
|
|
177
|
+
card('visitors', 'Visitors', toNumber(valueAt(goals, ['totals', 'visitors'])), 'goals'),
|
|
178
|
+
card('completions', 'Completions', toNumber(valueAt(goals, ['totals', 'completions'])), 'goals'),
|
|
179
|
+
card('topConversion', 'Top conversion', topConversion(goals.goals), 'goals', { unit: 'percent' })
|
|
180
|
+
],
|
|
181
|
+
funnels: [
|
|
182
|
+
card('funnels', 'Funnels', Array.isArray(funnels.funnels) ? funnels.funnels.length : 0, 'funnels'),
|
|
183
|
+
card('availableGoals', 'Available goals', Array.isArray(funnels.goals) ? funnels.goals.length : 0, 'funnels'),
|
|
184
|
+
card('bestConversion', 'Best conversion', bestFunnelConversion(funnels.funnels), 'funnels', { unit: 'percent' }),
|
|
185
|
+
card('lowestDropoff', 'Lowest dropoff', lowestFunnelDropoff(funnels.funnels), 'funnels', { unit: 'percent' })
|
|
186
|
+
],
|
|
187
|
+
events: [
|
|
188
|
+
card('events', 'Events', toNumber(valueAt(events, ['totals', 'events'])), 'events'),
|
|
189
|
+
card('visitors', 'Visitors', toNumber(valueAt(events, ['totals', 'visitors'])), 'events'),
|
|
190
|
+
card('visits', 'Visits', toNumber(valueAt(events, ['totals', 'visits'])), 'events'),
|
|
191
|
+
card('totalValue', 'Total value', toNumber(valueAt(events, ['totals', 'totalValue'])), 'events'),
|
|
192
|
+
card('averageDuration', 'Average duration', toNumber(valueAt(events, ['totals', 'averageDuration'])), 'events', {
|
|
193
|
+
unit: 'seconds'
|
|
194
|
+
})
|
|
195
|
+
],
|
|
196
|
+
performance: (Array.isArray(performance.metrics) ? performance.metrics : []).map((metric) =>
|
|
197
|
+
card(metric.key || metric.name || 'metric', metric.label || metric.key || 'Metric', toNumber(metric.p75), 'performance', {
|
|
198
|
+
unit: metric.unit || 'ms',
|
|
199
|
+
p50: toNumber(metric.p50),
|
|
200
|
+
p75: toNumber(metric.p75),
|
|
201
|
+
p95: toNumber(metric.p95),
|
|
202
|
+
samples: toNumber(metric.samples),
|
|
203
|
+
status: metric.status || ''
|
|
204
|
+
})
|
|
205
|
+
)
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
65
209
|
function cleanProperties(properties) {
|
|
66
210
|
if (!isPlainObject(properties)) return {}
|
|
67
211
|
|
|
@@ -207,13 +351,13 @@ function createState(options) {
|
|
|
207
351
|
const apiKey = cleanText(options.apiKey || options.api_key, 4096)
|
|
208
352
|
const siteUrl = cleanText(options.siteUrl || options.site_url || options.url, 2048)
|
|
209
353
|
|
|
210
|
-
if (!projectId) throw new WoolError('projectId is required.')
|
|
211
354
|
if (!apiKey) throw new WoolError('apiKey is required.')
|
|
212
355
|
|
|
213
356
|
return {
|
|
214
357
|
projectId,
|
|
215
358
|
apiKey,
|
|
216
359
|
endpoint: resolveEndpoint(options),
|
|
360
|
+
apiBaseUrl: resolveApiBaseUrl(options),
|
|
217
361
|
defaultUrl: siteUrl,
|
|
218
362
|
defaultReferrer: cleanText(options.referrer || options.referer, 2048),
|
|
219
363
|
enabled: options.enabled !== false,
|
|
@@ -229,9 +373,69 @@ function bodyFromResponse(response) {
|
|
|
229
373
|
return response?.text ? response.text().catch(() => '') : Promise.resolve('')
|
|
230
374
|
}
|
|
231
375
|
|
|
376
|
+
function parseJson(text) {
|
|
377
|
+
if (!text) return {}
|
|
378
|
+
|
|
379
|
+
try {
|
|
380
|
+
return JSON.parse(text)
|
|
381
|
+
} catch {
|
|
382
|
+
return null
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function normalizeRetentionGranularity(value) {
|
|
387
|
+
const text = String(value || '').trim().toLowerCase()
|
|
388
|
+
if (text === 'day' || text === 'daily') return 'daily'
|
|
389
|
+
if (text === 'week' || text === 'weekly') return 'weekly'
|
|
390
|
+
if (text === 'month' || text === 'monthly') return 'monthly'
|
|
391
|
+
return value
|
|
392
|
+
}
|
|
393
|
+
|
|
232
394
|
export function createClient(options = {}) {
|
|
233
395
|
const state = createState(options)
|
|
234
396
|
|
|
397
|
+
function projectIdFrom(options = {}) {
|
|
398
|
+
const projectId = cleanText(options.projectId || options.project_id || state.projectId, 96)
|
|
399
|
+
if (!projectId) throw new WoolError('projectId is required.')
|
|
400
|
+
return projectId
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function dataApiUrl(path, query = {}) {
|
|
404
|
+
if (!state.apiBaseUrl) {
|
|
405
|
+
throw new WoolError('apiBaseUrl, appUrl, dashboardUrl, or baseUrl is required for analytics reads.')
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
const url = new URL(path, `${state.apiBaseUrl}/`)
|
|
409
|
+
for (const [key, value] of Object.entries(query)) {
|
|
410
|
+
if (value !== undefined && value !== null && String(value).trim()) {
|
|
411
|
+
url.searchParams.set(key, String(value))
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
return url.toString()
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
async function requestJson(path, query = {}) {
|
|
419
|
+
const response = await state.fetch(dataApiUrl(path, query), {
|
|
420
|
+
method: 'GET',
|
|
421
|
+
headers: {
|
|
422
|
+
Accept: 'application/json',
|
|
423
|
+
Authorization: `Bearer ${state.apiKey}`,
|
|
424
|
+
'X-API-Key': state.apiKey,
|
|
425
|
+
'X-Wool-SDK': `@wool-so/node/${VERSION}`
|
|
426
|
+
}
|
|
427
|
+
})
|
|
428
|
+
const body = await bodyFromResponse(response)
|
|
429
|
+
const data = parseJson(body)
|
|
430
|
+
|
|
431
|
+
if (!response?.ok) {
|
|
432
|
+
throw new WoolError(data?.error || body || 'Unable to fetch Wool analytics data.', response?.status || 0, response || null)
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
if (data === null) throw new WoolError('Wool analytics response must be valid JSON.', response?.status || 0, response || null)
|
|
436
|
+
return data
|
|
437
|
+
}
|
|
438
|
+
|
|
235
439
|
async function send(payload) {
|
|
236
440
|
if (!state.enabled) {
|
|
237
441
|
return { ok: true, status: 0, skipped: true }
|
|
@@ -242,6 +446,7 @@ export function createClient(options = {}) {
|
|
|
242
446
|
headers: {
|
|
243
447
|
Authorization: `Bearer ${state.apiKey}`,
|
|
244
448
|
'Content-Type': 'application/json',
|
|
449
|
+
'X-API-Key': state.apiKey,
|
|
245
450
|
'X-Wool-SDK': `@wool-so/node/${VERSION}`
|
|
246
451
|
},
|
|
247
452
|
body: JSON.stringify(payload)
|
|
@@ -257,12 +462,14 @@ export function createClient(options = {}) {
|
|
|
257
462
|
|
|
258
463
|
function basePayload(type, options = {}) {
|
|
259
464
|
const context = requestContext(options.request || options.req, options)
|
|
465
|
+
const projectId = cleanText(options.projectId || options.project_id || state.projectId, 96)
|
|
260
466
|
const url = cleanText(context.url || state.defaultUrl, 2048)
|
|
261
467
|
|
|
468
|
+
if (!projectId) throw new WoolError('projectId is required.')
|
|
262
469
|
if (!url) throw new WoolError('url or siteUrl is required.')
|
|
263
470
|
|
|
264
471
|
const payload = {
|
|
265
|
-
projectId
|
|
472
|
+
projectId,
|
|
266
473
|
type,
|
|
267
474
|
url,
|
|
268
475
|
referrer: cleanText(context.referrer || state.defaultReferrer, 2048),
|
|
@@ -304,6 +511,97 @@ export function createClient(options = {}) {
|
|
|
304
511
|
return send(basePayload('pageview', options))
|
|
305
512
|
}
|
|
306
513
|
|
|
514
|
+
async function projects() {
|
|
515
|
+
return requestJson('/api/projects')
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
async function project(options = {}) {
|
|
519
|
+
const projectId = typeof options === 'string' ? cleanText(options, 96) : projectIdFrom(options)
|
|
520
|
+
if (!projectId) throw new WoolError('projectId is required.')
|
|
521
|
+
return requestJson(`/api/projects/${encodeURIComponent(projectId)}`)
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
function datasetQuery(name, options = {}) {
|
|
525
|
+
const normalized = String(name || '').toLowerCase()
|
|
526
|
+
const keys = DATASET_QUERY_KEYS[normalized] || []
|
|
527
|
+
const query = {}
|
|
528
|
+
|
|
529
|
+
for (const key of keys) {
|
|
530
|
+
if (key === 'event') {
|
|
531
|
+
query.event = options.event || options.eventName || options.selectedEvent
|
|
532
|
+
} else if (key === 'granularity') {
|
|
533
|
+
query.granularity = normalizeRetentionGranularity(options.granularity)
|
|
534
|
+
} else {
|
|
535
|
+
query[key] = options[key]
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
return query
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
async function dataset(name, options = {}) {
|
|
543
|
+
const normalized = String(name || '').toLowerCase()
|
|
544
|
+
if (!DATASET_NAMES.has(normalized)) throw new WoolError('Unsupported Wool dataset.')
|
|
545
|
+
|
|
546
|
+
const projectId = projectIdFrom(options)
|
|
547
|
+
return requestJson(
|
|
548
|
+
`/api/projects/${encodeURIComponent(projectId)}/web/${encodeURIComponent(normalized)}`,
|
|
549
|
+
datasetQuery(normalized, options)
|
|
550
|
+
)
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
const overview = (options = {}) => dataset('overview', options)
|
|
554
|
+
const performance = (options = {}) => dataset('performance', options)
|
|
555
|
+
const revenue = (options = {}) => dataset('revenue', options)
|
|
556
|
+
const events = (options = {}) => dataset('events', options)
|
|
557
|
+
const retention = (options = {}) => dataset('retention', options)
|
|
558
|
+
const goals = (options = {}) => dataset('goals', options)
|
|
559
|
+
const funnels = (options = {}) => dataset('funnels', options)
|
|
560
|
+
const realtime = (options = {}) => dataset('realtime', options)
|
|
561
|
+
|
|
562
|
+
async function datasets(options = {}) {
|
|
563
|
+
const [
|
|
564
|
+
overviewResult,
|
|
565
|
+
performanceResult,
|
|
566
|
+
revenueResult,
|
|
567
|
+
eventsResult,
|
|
568
|
+
retentionResult,
|
|
569
|
+
goalsResult,
|
|
570
|
+
funnelsResult,
|
|
571
|
+
realtimeResult
|
|
572
|
+
] = await Promise.all([
|
|
573
|
+
overview(options),
|
|
574
|
+
performance(options),
|
|
575
|
+
revenue(options),
|
|
576
|
+
events(options),
|
|
577
|
+
retention(options),
|
|
578
|
+
goals(options),
|
|
579
|
+
funnels(options),
|
|
580
|
+
realtime(options)
|
|
581
|
+
])
|
|
582
|
+
|
|
583
|
+
return {
|
|
584
|
+
project: overviewResult.project,
|
|
585
|
+
overview: overviewResult.overview,
|
|
586
|
+
performance: performanceResult.performance,
|
|
587
|
+
revenue: revenueResult.revenue,
|
|
588
|
+
events: eventsResult.events,
|
|
589
|
+
retention: retentionResult.retention,
|
|
590
|
+
goals: goalsResult.goals,
|
|
591
|
+
funnels: funnelsResult.funnels,
|
|
592
|
+
realtime: realtimeResult.realtime
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
async function cards(options = {}) {
|
|
597
|
+
const data = await datasets(options)
|
|
598
|
+
return {
|
|
599
|
+
project: data.project,
|
|
600
|
+
cards: buildCards(data),
|
|
601
|
+
datasets: data
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
307
605
|
function identify(retentionId, options = {}) {
|
|
308
606
|
state.identity = {
|
|
309
607
|
retentionId: cleanText(retentionId),
|
|
@@ -319,8 +617,21 @@ export function createClient(options = {}) {
|
|
|
319
617
|
}
|
|
320
618
|
|
|
321
619
|
return {
|
|
620
|
+
cards,
|
|
621
|
+
dataset,
|
|
622
|
+
datasets,
|
|
623
|
+
events,
|
|
624
|
+
funnels,
|
|
625
|
+
goals,
|
|
322
626
|
identify,
|
|
627
|
+
overview,
|
|
323
628
|
pageview,
|
|
629
|
+
performance,
|
|
630
|
+
project,
|
|
631
|
+
projects,
|
|
632
|
+
realtime,
|
|
633
|
+
retention,
|
|
634
|
+
revenue,
|
|
324
635
|
requestContext,
|
|
325
636
|
reset,
|
|
326
637
|
track
|
|
@@ -387,12 +698,77 @@ export function pageview(options) {
|
|
|
387
698
|
return requireDefaultClient().pageview(options)
|
|
388
699
|
}
|
|
389
700
|
|
|
701
|
+
export function projects() {
|
|
702
|
+
return requireDefaultClient().projects()
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
export function project(options) {
|
|
706
|
+
return requireDefaultClient().project(options)
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
export function dataset(name, options) {
|
|
710
|
+
return requireDefaultClient().dataset(name, options)
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
export function datasets(options) {
|
|
714
|
+
return requireDefaultClient().datasets(options)
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
export function cards(options) {
|
|
718
|
+
return requireDefaultClient().cards(options)
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
export function overview(options) {
|
|
722
|
+
return requireDefaultClient().overview(options)
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
export function performance(options) {
|
|
726
|
+
return requireDefaultClient().performance(options)
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
export function revenue(options) {
|
|
730
|
+
return requireDefaultClient().revenue(options)
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
export function events(options) {
|
|
734
|
+
return requireDefaultClient().events(options)
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
export function retention(options) {
|
|
738
|
+
return requireDefaultClient().retention(options)
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
export function goals(options) {
|
|
742
|
+
return requireDefaultClient().goals(options)
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
export function funnels(options) {
|
|
746
|
+
return requireDefaultClient().funnels(options)
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
export function realtime(options) {
|
|
750
|
+
return requireDefaultClient().realtime(options)
|
|
751
|
+
}
|
|
752
|
+
|
|
390
753
|
export const wool = {
|
|
391
754
|
init,
|
|
755
|
+
cards,
|
|
756
|
+
dataset,
|
|
757
|
+
datasets,
|
|
758
|
+
events,
|
|
759
|
+
funnels,
|
|
760
|
+
goals,
|
|
392
761
|
identify,
|
|
393
762
|
reset,
|
|
763
|
+
overview,
|
|
394
764
|
track,
|
|
395
765
|
pageview,
|
|
766
|
+
performance,
|
|
767
|
+
project,
|
|
768
|
+
projects,
|
|
769
|
+
realtime,
|
|
770
|
+
retention,
|
|
771
|
+
revenue,
|
|
396
772
|
createClient,
|
|
397
773
|
requestContext,
|
|
398
774
|
expressMiddleware,
|