@sessionsight/goals 1.0.0 → 1.0.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/dist/client.d.ts +10 -0
- package/dist/client.test.d.ts +1 -0
- package/dist/iife.d.ts +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +142 -0
- package/dist/types.d.ts +21 -0
- package/package.json +4 -1
- package/src/client.ts +0 -104
- package/src/index.ts +0 -42
- package/src/types.ts +0 -24
- package/test/client.test.ts +0 -198
- package/tsconfig.json +0 -13
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { GoalsConfig, IncrementOptions, DecrementOptions, IncrementResult } from './types.js';
|
|
2
|
+
export declare class GoalsClient {
|
|
3
|
+
private apiUrl;
|
|
4
|
+
private secretApiKey;
|
|
5
|
+
private propertyId;
|
|
6
|
+
constructor(config: GoalsConfig);
|
|
7
|
+
increment(goalId: string, options?: IncrementOptions): Promise<IncrementResult>;
|
|
8
|
+
decrement(goalId: string, options?: DecrementOptions): Promise<IncrementResult>;
|
|
9
|
+
destroy(): void;
|
|
10
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/iife.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { GoalsClient } from './client.js';
|
|
2
|
+
import type { GoalsConfig, IncrementOptions, DecrementOptions, IncrementResult } from './types.js';
|
|
3
|
+
export { GoalsClient };
|
|
4
|
+
export type { GoalsConfig, IncrementOptions, DecrementOptions, IncrementResult } from './types.js';
|
|
5
|
+
declare const SessionSightGoals: {
|
|
6
|
+
init(config: GoalsConfig): void;
|
|
7
|
+
increment(goalId: string, options?: IncrementOptions): Promise<IncrementResult>;
|
|
8
|
+
decrement(goalId: string, options?: DecrementOptions): Promise<IncrementResult>;
|
|
9
|
+
destroy(): void;
|
|
10
|
+
};
|
|
11
|
+
export default SessionSightGoals;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
// ../sdk-shared/src/index.ts
|
|
2
|
+
var DEFAULT_API_URL = "https://api.sessionsight.com";
|
|
3
|
+
function normalizeApiUrl(url) {
|
|
4
|
+
const normalized = (url || DEFAULT_API_URL).replace(/\/$/, "");
|
|
5
|
+
if (normalized.startsWith("http://") && !normalized.startsWith("http://localhost")) {
|
|
6
|
+
console.warn("SessionSight: API URL uses http:// instead of https://. Data will be transmitted unencrypted.");
|
|
7
|
+
}
|
|
8
|
+
return normalized;
|
|
9
|
+
}
|
|
10
|
+
var VISITOR_COOKIE_MAX_AGE = 365 * 24 * 60 * 60;
|
|
11
|
+
var registry = new Map;
|
|
12
|
+
|
|
13
|
+
// src/client.ts
|
|
14
|
+
var FETCH_TIMEOUT_MS = 1e4;
|
|
15
|
+
function fetchWithTimeout(url, options) {
|
|
16
|
+
const controller = new AbortController;
|
|
17
|
+
const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
18
|
+
return fetch(url, { ...options, signal: controller.signal }).finally(() => clearTimeout(timer));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
class GoalsClient {
|
|
22
|
+
apiUrl;
|
|
23
|
+
secretApiKey;
|
|
24
|
+
propertyId;
|
|
25
|
+
constructor(config) {
|
|
26
|
+
if (typeof window !== "undefined" && !("process" in globalThis)) {
|
|
27
|
+
throw new Error("@sessionsight/goals is a server-side SDK and cannot be used in the browser.");
|
|
28
|
+
}
|
|
29
|
+
if (!config.secretApiKey?.trim())
|
|
30
|
+
throw new Error("@sessionsight/goals: secretApiKey is required.");
|
|
31
|
+
if (!config.propertyId?.trim())
|
|
32
|
+
throw new Error("@sessionsight/goals: propertyId is required.");
|
|
33
|
+
this.secretApiKey = config.secretApiKey;
|
|
34
|
+
this.propertyId = config.propertyId;
|
|
35
|
+
this.apiUrl = normalizeApiUrl(config.apiUrl || "");
|
|
36
|
+
}
|
|
37
|
+
async increment(goalId, options) {
|
|
38
|
+
const amount = options?.amount ?? 1;
|
|
39
|
+
if (!Number.isFinite(amount) || amount <= 0) {
|
|
40
|
+
return { success: false, error: "amount must be a positive finite number" };
|
|
41
|
+
}
|
|
42
|
+
if (options?.value != null && (!Number.isFinite(options.value) || options.value < 0)) {
|
|
43
|
+
return { success: false, error: "value must be a non-negative finite number" };
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
const res = await fetchWithTimeout(`${this.apiUrl}/v1/goals/increment`, {
|
|
47
|
+
method: "POST",
|
|
48
|
+
headers: {
|
|
49
|
+
"Content-Type": "application/json",
|
|
50
|
+
"x-api-key": this.secretApiKey
|
|
51
|
+
},
|
|
52
|
+
body: JSON.stringify({
|
|
53
|
+
goalId,
|
|
54
|
+
propertyId: this.propertyId,
|
|
55
|
+
amount: options?.amount ?? 1,
|
|
56
|
+
...options?.value != null ? { value: options.value } : {},
|
|
57
|
+
...options?.currency ? { currency: options.currency } : {},
|
|
58
|
+
...options?.visitorId ? { visitorId: options.visitorId } : {},
|
|
59
|
+
...options?.metadata ? { metadata: options.metadata } : {}
|
|
60
|
+
})
|
|
61
|
+
});
|
|
62
|
+
if (!res.ok) {
|
|
63
|
+
const data = await res.json().catch(() => ({}));
|
|
64
|
+
return { success: false, error: data.error || `HTTP ${res.status}` };
|
|
65
|
+
}
|
|
66
|
+
return { success: true };
|
|
67
|
+
} catch (err) {
|
|
68
|
+
const message = err instanceof Error ? err.message : "Unknown error";
|
|
69
|
+
console.warn("[SessionSight Goals] Failed to increment goal:", message);
|
|
70
|
+
return { success: false, error: message };
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
async decrement(goalId, options) {
|
|
74
|
+
const amount = options?.amount ?? 1;
|
|
75
|
+
if (!Number.isFinite(amount) || amount <= 0) {
|
|
76
|
+
return { success: false, error: "amount must be a positive finite number" };
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
const res = await fetchWithTimeout(`${this.apiUrl}/v1/goals/decrement`, {
|
|
80
|
+
method: "POST",
|
|
81
|
+
headers: {
|
|
82
|
+
"Content-Type": "application/json",
|
|
83
|
+
"x-api-key": this.secretApiKey
|
|
84
|
+
},
|
|
85
|
+
body: JSON.stringify({
|
|
86
|
+
goalId,
|
|
87
|
+
propertyId: this.propertyId,
|
|
88
|
+
amount: options?.amount ?? 1,
|
|
89
|
+
...options?.visitorId ? { visitorId: options.visitorId } : {},
|
|
90
|
+
...options?.metadata ? { metadata: options.metadata } : {}
|
|
91
|
+
})
|
|
92
|
+
});
|
|
93
|
+
if (!res.ok) {
|
|
94
|
+
const data = await res.json().catch(() => ({}));
|
|
95
|
+
return { success: false, error: data.error || `HTTP ${res.status}` };
|
|
96
|
+
}
|
|
97
|
+
return { success: true };
|
|
98
|
+
} catch (err) {
|
|
99
|
+
const message = err instanceof Error ? err.message : "Unknown error";
|
|
100
|
+
console.warn("[SessionSight Goals] Failed to decrement goal:", message);
|
|
101
|
+
return { success: false, error: message };
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
destroy() {}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// src/index.ts
|
|
108
|
+
var instance = null;
|
|
109
|
+
var SessionSightGoals = {
|
|
110
|
+
init(config) {
|
|
111
|
+
if (instance) {
|
|
112
|
+
console.warn("[SessionSight Goals] Already initialized. Call destroy() first.");
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
instance = new GoalsClient(config);
|
|
116
|
+
},
|
|
117
|
+
async increment(goalId, options) {
|
|
118
|
+
if (!instance) {
|
|
119
|
+
console.warn("[SessionSight Goals] Not initialized. Call init() first.");
|
|
120
|
+
return { success: false, error: "Not initialized" };
|
|
121
|
+
}
|
|
122
|
+
return instance.increment(goalId, options);
|
|
123
|
+
},
|
|
124
|
+
async decrement(goalId, options) {
|
|
125
|
+
if (!instance) {
|
|
126
|
+
console.warn("[SessionSight Goals] Not initialized. Call init() first.");
|
|
127
|
+
return { success: false, error: "Not initialized" };
|
|
128
|
+
}
|
|
129
|
+
return instance.decrement(goalId, options);
|
|
130
|
+
},
|
|
131
|
+
destroy() {
|
|
132
|
+
if (instance) {
|
|
133
|
+
instance.destroy();
|
|
134
|
+
instance = null;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
var src_default = SessionSightGoals;
|
|
139
|
+
export {
|
|
140
|
+
src_default as default,
|
|
141
|
+
GoalsClient
|
|
142
|
+
};
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface GoalsConfig {
|
|
2
|
+
secretApiKey: string;
|
|
3
|
+
propertyId: string;
|
|
4
|
+
apiUrl?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface IncrementOptions {
|
|
7
|
+
amount?: number;
|
|
8
|
+
value?: number;
|
|
9
|
+
currency?: string;
|
|
10
|
+
visitorId?: string;
|
|
11
|
+
metadata?: Record<string, string>;
|
|
12
|
+
}
|
|
13
|
+
export interface DecrementOptions {
|
|
14
|
+
amount?: number;
|
|
15
|
+
visitorId?: string;
|
|
16
|
+
metadata?: Record<string, string>;
|
|
17
|
+
}
|
|
18
|
+
export interface IncrementResult {
|
|
19
|
+
success: boolean;
|
|
20
|
+
error?: string;
|
|
21
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sessionsight/goals",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Goal and conversion tracking SDK for SessionSight.",
|
|
5
5
|
"author": "SessionSight",
|
|
6
6
|
"license": "MIT",
|
|
@@ -19,6 +19,9 @@
|
|
|
19
19
|
"analytics",
|
|
20
20
|
"sessionsight"
|
|
21
21
|
],
|
|
22
|
+
"files": [
|
|
23
|
+
"dist"
|
|
24
|
+
],
|
|
22
25
|
"type": "module",
|
|
23
26
|
"main": "./dist/index.js",
|
|
24
27
|
"types": "./dist/index.d.ts",
|
package/src/client.ts
DELETED
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import type { GoalsConfig, IncrementOptions, DecrementOptions, IncrementResult } from './types.js';
|
|
2
|
-
import { normalizeApiUrl } from '@sessionsight/sdk-shared';
|
|
3
|
-
|
|
4
|
-
const FETCH_TIMEOUT_MS = 10_000;
|
|
5
|
-
|
|
6
|
-
function fetchWithTimeout(url: string, options: RequestInit): Promise<Response> {
|
|
7
|
-
const controller = new AbortController();
|
|
8
|
-
const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
9
|
-
return fetch(url, { ...options, signal: controller.signal }).finally(() => clearTimeout(timer));
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export class GoalsClient {
|
|
13
|
-
private apiUrl: string;
|
|
14
|
-
private secretApiKey: string;
|
|
15
|
-
private propertyId: string;
|
|
16
|
-
|
|
17
|
-
constructor(config: GoalsConfig) {
|
|
18
|
-
if (typeof window !== 'undefined' && !('process' in globalThis)) {
|
|
19
|
-
throw new Error('@sessionsight/goals is a server-side SDK and cannot be used in the browser.');
|
|
20
|
-
}
|
|
21
|
-
if (!config.secretApiKey?.trim()) throw new Error('@sessionsight/goals: secretApiKey is required.');
|
|
22
|
-
if (!config.propertyId?.trim()) throw new Error('@sessionsight/goals: propertyId is required.');
|
|
23
|
-
this.secretApiKey = config.secretApiKey;
|
|
24
|
-
this.propertyId = config.propertyId;
|
|
25
|
-
this.apiUrl = normalizeApiUrl(config.apiUrl || '');
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
async increment(goalId: string, options?: IncrementOptions): Promise<IncrementResult> {
|
|
29
|
-
const amount = options?.amount ?? 1;
|
|
30
|
-
if (!Number.isFinite(amount) || amount <= 0) {
|
|
31
|
-
return { success: false, error: 'amount must be a positive finite number' };
|
|
32
|
-
}
|
|
33
|
-
if (options?.value != null && (!Number.isFinite(options.value) || options.value < 0)) {
|
|
34
|
-
return { success: false, error: 'value must be a non-negative finite number' };
|
|
35
|
-
}
|
|
36
|
-
try {
|
|
37
|
-
const res = await fetchWithTimeout(`${this.apiUrl}/v1/goals/increment`, {
|
|
38
|
-
method: 'POST',
|
|
39
|
-
headers: {
|
|
40
|
-
'Content-Type': 'application/json',
|
|
41
|
-
'x-api-key': this.secretApiKey,
|
|
42
|
-
},
|
|
43
|
-
body: JSON.stringify({
|
|
44
|
-
goalId,
|
|
45
|
-
propertyId: this.propertyId,
|
|
46
|
-
amount: options?.amount ?? 1,
|
|
47
|
-
...(options?.value != null ? { value: options.value } : {}),
|
|
48
|
-
...(options?.currency ? { currency: options.currency } : {}),
|
|
49
|
-
...(options?.visitorId ? { visitorId: options.visitorId } : {}),
|
|
50
|
-
...(options?.metadata ? { metadata: options.metadata } : {}),
|
|
51
|
-
}),
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
if (!res.ok) {
|
|
55
|
-
const data = await res.json().catch(() => ({}));
|
|
56
|
-
return { success: false, error: (data as any).error || `HTTP ${res.status}` };
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return { success: true };
|
|
60
|
-
} catch (err) {
|
|
61
|
-
const message = err instanceof Error ? err.message : 'Unknown error';
|
|
62
|
-
console.warn('[SessionSight Goals] Failed to increment goal:', message);
|
|
63
|
-
return { success: false, error: message };
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
async decrement(goalId: string, options?: DecrementOptions): Promise<IncrementResult> {
|
|
68
|
-
const amount = options?.amount ?? 1;
|
|
69
|
-
if (!Number.isFinite(amount) || amount <= 0) {
|
|
70
|
-
return { success: false, error: 'amount must be a positive finite number' };
|
|
71
|
-
}
|
|
72
|
-
try {
|
|
73
|
-
const res = await fetchWithTimeout(`${this.apiUrl}/v1/goals/decrement`, {
|
|
74
|
-
method: 'POST',
|
|
75
|
-
headers: {
|
|
76
|
-
'Content-Type': 'application/json',
|
|
77
|
-
'x-api-key': this.secretApiKey,
|
|
78
|
-
},
|
|
79
|
-
body: JSON.stringify({
|
|
80
|
-
goalId,
|
|
81
|
-
propertyId: this.propertyId,
|
|
82
|
-
amount: options?.amount ?? 1,
|
|
83
|
-
...(options?.visitorId ? { visitorId: options.visitorId } : {}),
|
|
84
|
-
...(options?.metadata ? { metadata: options.metadata } : {}),
|
|
85
|
-
}),
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
if (!res.ok) {
|
|
89
|
-
const data = await res.json().catch(() => ({}));
|
|
90
|
-
return { success: false, error: (data as any).error || `HTTP ${res.status}` };
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
return { success: true };
|
|
94
|
-
} catch (err) {
|
|
95
|
-
const message = err instanceof Error ? err.message : 'Unknown error';
|
|
96
|
-
console.warn('[SessionSight Goals] Failed to decrement goal:', message);
|
|
97
|
-
return { success: false, error: message };
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
destroy(): void {
|
|
102
|
-
// No-op for now; reserved for future cleanup (e.g., batching, intervals)
|
|
103
|
-
}
|
|
104
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { GoalsClient } from './client.js';
|
|
2
|
-
import type { GoalsConfig, IncrementOptions, DecrementOptions, IncrementResult } from './types.js';
|
|
3
|
-
|
|
4
|
-
export { GoalsClient };
|
|
5
|
-
export type { GoalsConfig, IncrementOptions, DecrementOptions, IncrementResult } from './types.js';
|
|
6
|
-
|
|
7
|
-
let instance: GoalsClient | null = null;
|
|
8
|
-
|
|
9
|
-
const SessionSightGoals = {
|
|
10
|
-
init(config: GoalsConfig): void {
|
|
11
|
-
if (instance) {
|
|
12
|
-
console.warn('[SessionSight Goals] Already initialized. Call destroy() first.');
|
|
13
|
-
return;
|
|
14
|
-
}
|
|
15
|
-
instance = new GoalsClient(config);
|
|
16
|
-
},
|
|
17
|
-
|
|
18
|
-
async increment(goalId: string, options?: IncrementOptions): Promise<IncrementResult> {
|
|
19
|
-
if (!instance) {
|
|
20
|
-
console.warn('[SessionSight Goals] Not initialized. Call init() first.');
|
|
21
|
-
return { success: false, error: 'Not initialized' };
|
|
22
|
-
}
|
|
23
|
-
return instance.increment(goalId, options);
|
|
24
|
-
},
|
|
25
|
-
|
|
26
|
-
async decrement(goalId: string, options?: DecrementOptions): Promise<IncrementResult> {
|
|
27
|
-
if (!instance) {
|
|
28
|
-
console.warn('[SessionSight Goals] Not initialized. Call init() first.');
|
|
29
|
-
return { success: false, error: 'Not initialized' };
|
|
30
|
-
}
|
|
31
|
-
return instance.decrement(goalId, options);
|
|
32
|
-
},
|
|
33
|
-
|
|
34
|
-
destroy(): void {
|
|
35
|
-
if (instance) {
|
|
36
|
-
instance.destroy();
|
|
37
|
-
instance = null;
|
|
38
|
-
}
|
|
39
|
-
},
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
export default SessionSightGoals;
|
package/src/types.ts
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
export interface GoalsConfig {
|
|
2
|
-
secretApiKey: string;
|
|
3
|
-
propertyId: string;
|
|
4
|
-
apiUrl?: string;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export interface IncrementOptions {
|
|
8
|
-
amount?: number;
|
|
9
|
-
value?: number;
|
|
10
|
-
currency?: string;
|
|
11
|
-
visitorId?: string;
|
|
12
|
-
metadata?: Record<string, string>;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface DecrementOptions {
|
|
16
|
-
amount?: number;
|
|
17
|
-
visitorId?: string;
|
|
18
|
-
metadata?: Record<string, string>;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export interface IncrementResult {
|
|
22
|
-
success: boolean;
|
|
23
|
-
error?: string;
|
|
24
|
-
}
|
package/test/client.test.ts
DELETED
|
@@ -1,198 +0,0 @@
|
|
|
1
|
-
import { test, expect, mock, beforeEach } from 'bun:test';
|
|
2
|
-
import { GoalsClient } from '../src/client.js';
|
|
3
|
-
|
|
4
|
-
const originalFetch = globalThis.fetch;
|
|
5
|
-
|
|
6
|
-
beforeEach(() => {
|
|
7
|
-
globalThis.fetch = originalFetch;
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
test('increment sends correct request', async () => {
|
|
11
|
-
const mockFetch = mock(() =>
|
|
12
|
-
Promise.resolve(new Response(JSON.stringify({ success: true }), { status: 200 }))
|
|
13
|
-
);
|
|
14
|
-
globalThis.fetch = mockFetch;
|
|
15
|
-
|
|
16
|
-
const client = new GoalsClient({
|
|
17
|
-
secretApiKey: 'sk_test_123',
|
|
18
|
-
propertyId: 'prop-1',
|
|
19
|
-
apiUrl: 'https://api.example.com',
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
const result = await client.increment('user-signups');
|
|
23
|
-
|
|
24
|
-
expect(result).toEqual({ success: true });
|
|
25
|
-
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
26
|
-
|
|
27
|
-
const [url, opts] = mockFetch.mock.calls[0] as [string, RequestInit];
|
|
28
|
-
expect(url).toBe('https://api.example.com/v1/goals/increment');
|
|
29
|
-
expect(opts.method).toBe('POST');
|
|
30
|
-
expect(opts.headers).toEqual({
|
|
31
|
-
'Content-Type': 'application/json',
|
|
32
|
-
'x-api-key': 'sk_test_123',
|
|
33
|
-
});
|
|
34
|
-
const body = JSON.parse(opts.body as string);
|
|
35
|
-
expect(body.goalId).toBe('user-signups');
|
|
36
|
-
expect(body.propertyId).toBe('prop-1');
|
|
37
|
-
expect(body.amount).toBe(1);
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
test('increment sends custom amount and metadata', async () => {
|
|
41
|
-
const mockFetch = mock(() =>
|
|
42
|
-
Promise.resolve(new Response(JSON.stringify({ success: true }), { status: 200 }))
|
|
43
|
-
);
|
|
44
|
-
globalThis.fetch = mockFetch;
|
|
45
|
-
|
|
46
|
-
const client = new GoalsClient({
|
|
47
|
-
secretApiKey: 'sk_test_123',
|
|
48
|
-
propertyId: 'prop-1',
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
await client.increment('revenue', { amount: 49, metadata: { plan: 'pro' } });
|
|
52
|
-
|
|
53
|
-
const body = JSON.parse((mockFetch.mock.calls[0] as [string, RequestInit])[1].body as string);
|
|
54
|
-
expect(body.amount).toBe(49);
|
|
55
|
-
expect(body.metadata).toEqual({ plan: 'pro' });
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
test('increment returns error on non-ok response', async () => {
|
|
59
|
-
globalThis.fetch = mock(() =>
|
|
60
|
-
Promise.resolve(new Response(JSON.stringify({ error: 'Goal not found' }), { status: 404 }))
|
|
61
|
-
);
|
|
62
|
-
|
|
63
|
-
const client = new GoalsClient({
|
|
64
|
-
secretApiKey: 'sk_test_123',
|
|
65
|
-
propertyId: 'prop-1',
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
const result = await client.increment('nonexistent');
|
|
69
|
-
expect(result.success).toBe(false);
|
|
70
|
-
expect(result.error).toBe('Goal not found');
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
test('increment returns error on network failure', async () => {
|
|
74
|
-
globalThis.fetch = mock(() => Promise.reject(new Error('Network error')));
|
|
75
|
-
|
|
76
|
-
const client = new GoalsClient({
|
|
77
|
-
secretApiKey: 'sk_test_123',
|
|
78
|
-
propertyId: 'prop-1',
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
const result = await client.increment('user-signups');
|
|
82
|
-
expect(result.success).toBe(false);
|
|
83
|
-
expect(result.error).toBe('Network error');
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
test('strips trailing slash from apiUrl', async () => {
|
|
87
|
-
const mockFetch = mock(() =>
|
|
88
|
-
Promise.resolve(new Response(JSON.stringify({ success: true }), { status: 200 }))
|
|
89
|
-
);
|
|
90
|
-
globalThis.fetch = mockFetch;
|
|
91
|
-
|
|
92
|
-
const client = new GoalsClient({
|
|
93
|
-
secretApiKey: 'sk_test_123',
|
|
94
|
-
propertyId: 'prop-1',
|
|
95
|
-
apiUrl: 'https://api.example.com/',
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
await client.increment('test');
|
|
99
|
-
const url = (mockFetch.mock.calls[0] as [string, RequestInit])[0];
|
|
100
|
-
expect(url).toBe('https://api.example.com/v1/goals/increment');
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
test('increment sends value and currency for revenue attribution', async () => {
|
|
104
|
-
const mockFetch = mock(() =>
|
|
105
|
-
Promise.resolve(new Response(JSON.stringify({ success: true }), { status: 200 }))
|
|
106
|
-
);
|
|
107
|
-
globalThis.fetch = mockFetch;
|
|
108
|
-
|
|
109
|
-
const client = new GoalsClient({
|
|
110
|
-
secretApiKey: 'sk_test_123',
|
|
111
|
-
propertyId: 'prop-1',
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
await client.increment('purchase', { value: 49.99, currency: 'USD', metadata: { sku: 'widget-pro' } });
|
|
115
|
-
|
|
116
|
-
const body = JSON.parse((mockFetch.mock.calls[0] as [string, RequestInit])[1].body as string);
|
|
117
|
-
expect(body.value).toBe(49.99);
|
|
118
|
-
expect(body.currency).toBe('USD');
|
|
119
|
-
expect(body.metadata).toEqual({ sku: 'widget-pro' });
|
|
120
|
-
expect(body.amount).toBe(1);
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
test('increment omits value and currency when not provided', async () => {
|
|
124
|
-
const mockFetch = mock(() =>
|
|
125
|
-
Promise.resolve(new Response(JSON.stringify({ success: true }), { status: 200 }))
|
|
126
|
-
);
|
|
127
|
-
globalThis.fetch = mockFetch;
|
|
128
|
-
|
|
129
|
-
const client = new GoalsClient({
|
|
130
|
-
secretApiKey: 'sk_test_123',
|
|
131
|
-
propertyId: 'prop-1',
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
await client.increment('user-signups', { amount: 5 });
|
|
135
|
-
|
|
136
|
-
const body = JSON.parse((mockFetch.mock.calls[0] as [string, RequestInit])[1].body as string);
|
|
137
|
-
expect(body.value).toBeUndefined();
|
|
138
|
-
expect(body.currency).toBeUndefined();
|
|
139
|
-
expect(body.amount).toBe(5);
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
test('decrement sends correct request', async () => {
|
|
143
|
-
const mockFetch = mock(() =>
|
|
144
|
-
Promise.resolve(new Response(JSON.stringify({ success: true }), { status: 200 }))
|
|
145
|
-
);
|
|
146
|
-
globalThis.fetch = mockFetch;
|
|
147
|
-
|
|
148
|
-
const client = new GoalsClient({
|
|
149
|
-
secretApiKey: 'sk_test_123',
|
|
150
|
-
propertyId: 'prop-1',
|
|
151
|
-
apiUrl: 'https://api.example.com',
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
const result = await client.decrement('user-signups');
|
|
155
|
-
|
|
156
|
-
expect(result).toEqual({ success: true });
|
|
157
|
-
const [url, opts] = mockFetch.mock.calls[0] as [string, RequestInit];
|
|
158
|
-
expect(url).toBe('https://api.example.com/v1/goals/decrement');
|
|
159
|
-
expect(opts.method).toBe('POST');
|
|
160
|
-
const body = JSON.parse(opts.body as string);
|
|
161
|
-
expect(body.goalId).toBe('user-signups');
|
|
162
|
-
expect(body.propertyId).toBe('prop-1');
|
|
163
|
-
expect(body.amount).toBe(1);
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
test('decrement sends custom amount and metadata', async () => {
|
|
167
|
-
const mockFetch = mock(() =>
|
|
168
|
-
Promise.resolve(new Response(JSON.stringify({ success: true }), { status: 200 }))
|
|
169
|
-
);
|
|
170
|
-
globalThis.fetch = mockFetch;
|
|
171
|
-
|
|
172
|
-
const client = new GoalsClient({
|
|
173
|
-
secretApiKey: 'sk_test_123',
|
|
174
|
-
propertyId: 'prop-1',
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
await client.decrement('inventory', { amount: 3, metadata: { reason: 'refund' } });
|
|
178
|
-
|
|
179
|
-
const body = JSON.parse((mockFetch.mock.calls[0] as [string, RequestInit])[1].body as string);
|
|
180
|
-
expect(body.amount).toBe(3);
|
|
181
|
-
expect(body.metadata).toEqual({ reason: 'refund' });
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
test('uses default apiUrl when not provided', async () => {
|
|
185
|
-
const mockFetch = mock(() =>
|
|
186
|
-
Promise.resolve(new Response(JSON.stringify({ success: true }), { status: 200 }))
|
|
187
|
-
);
|
|
188
|
-
globalThis.fetch = mockFetch;
|
|
189
|
-
|
|
190
|
-
const client = new GoalsClient({
|
|
191
|
-
secretApiKey: 'sk_test_123',
|
|
192
|
-
propertyId: 'prop-1',
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
await client.increment('test');
|
|
196
|
-
const url = (mockFetch.mock.calls[0] as [string, RequestInit])[0];
|
|
197
|
-
expect(url).toBe('https://api.sessionsight.com/v1/goals/increment');
|
|
198
|
-
});
|
package/tsconfig.json
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"extends": "../../tsconfig.json",
|
|
3
|
-
"compilerOptions": {
|
|
4
|
-
"outDir": "./dist",
|
|
5
|
-
"declaration": true,
|
|
6
|
-
"noEmit": false,
|
|
7
|
-
"rootDir": "./src",
|
|
8
|
-
"lib": ["ES2022", "DOM"],
|
|
9
|
-
"module": "ESNext",
|
|
10
|
-
"moduleResolution": "bundler"
|
|
11
|
-
},
|
|
12
|
-
"include": ["src/**/*.ts"]
|
|
13
|
-
}
|