@linkforty/core 1.0.0 → 1.2.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/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +26 -43
- package/dist/index.js.map +1 -1
- package/dist/lib/database.d.ts.map +1 -1
- package/dist/lib/database.js +160 -12
- package/dist/lib/database.js.map +1 -1
- package/dist/lib/event-emitter.d.ts +46 -0
- package/dist/lib/event-emitter.d.ts.map +1 -0
- package/dist/lib/event-emitter.js +24 -0
- package/dist/lib/event-emitter.js.map +1 -0
- package/dist/lib/fingerprint.d.ts +64 -0
- package/dist/lib/fingerprint.d.ts.map +1 -0
- package/dist/lib/fingerprint.js +343 -0
- package/dist/lib/fingerprint.js.map +1 -0
- package/dist/lib/utils.d.ts +1 -0
- package/dist/lib/utils.d.ts.map +1 -1
- package/dist/lib/utils.js +12 -21
- package/dist/lib/utils.js.map +1 -1
- package/dist/lib/webhook.d.ts +18 -0
- package/dist/lib/webhook.d.ts.map +1 -0
- package/dist/lib/webhook.js +141 -0
- package/dist/lib/webhook.js.map +1 -0
- package/dist/routes/analytics.js +14 -17
- package/dist/routes/analytics.js.map +1 -1
- package/dist/routes/debug.d.ts +7 -0
- package/dist/routes/debug.d.ts.map +1 -0
- package/dist/routes/debug.js +318 -0
- package/dist/routes/debug.js.map +1 -0
- package/dist/routes/index.d.ts +5 -0
- package/dist/routes/index.d.ts.map +1 -1
- package/dist/routes/index.js +8 -9
- package/dist/routes/index.js.map +1 -1
- package/dist/routes/links.d.ts.map +1 -1
- package/dist/routes/links.js +53 -38
- package/dist/routes/links.js.map +1 -1
- package/dist/routes/preview.d.ts +3 -0
- package/dist/routes/preview.d.ts.map +1 -0
- package/dist/routes/preview.js +222 -0
- package/dist/routes/preview.js.map +1 -0
- package/dist/routes/qr.d.ts +6 -0
- package/dist/routes/qr.d.ts.map +1 -0
- package/dist/routes/qr.js +130 -0
- package/dist/routes/qr.js.map +1 -0
- package/dist/routes/redirect.d.ts.map +1 -1
- package/dist/routes/redirect.js +142 -22
- package/dist/routes/redirect.js.map +1 -1
- package/dist/routes/sdk.d.ts +7 -0
- package/dist/routes/sdk.d.ts.map +1 -0
- package/dist/routes/sdk.js +262 -0
- package/dist/routes/sdk.js.map +1 -0
- package/dist/routes/webhooks.d.ts +3 -0
- package/dist/routes/webhooks.d.ts.map +1 -0
- package/dist/routes/webhooks.js +176 -0
- package/dist/routes/webhooks.js.map +1 -0
- package/dist/scripts/migrate.js +2 -4
- package/dist/scripts/migrate.js.map +1 -1
- package/dist/types/index.d.ts +81 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +1 -2
- package/package.json +11 -7
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
import crypto from 'crypto';
|
|
2
|
+
import { db } from './database.js';
|
|
3
|
+
/**
|
|
4
|
+
* Scoring weights for probabilistic matching
|
|
5
|
+
* Total should equal 100 for percentage-based confidence
|
|
6
|
+
*/
|
|
7
|
+
const FINGERPRINT_WEIGHTS = {
|
|
8
|
+
IP_ADDRESS: 40,
|
|
9
|
+
USER_AGENT: 30,
|
|
10
|
+
TIMEZONE: 10,
|
|
11
|
+
LANGUAGE: 10,
|
|
12
|
+
SCREEN_RESOLUTION: 10,
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Default attribution window in hours (7 days)
|
|
16
|
+
*/
|
|
17
|
+
export const DEFAULT_ATTRIBUTION_WINDOW_HOURS = 168;
|
|
18
|
+
/**
|
|
19
|
+
* Minimum confidence threshold for attribution (70%)
|
|
20
|
+
*/
|
|
21
|
+
export const CONFIDENCE_THRESHOLD = 70;
|
|
22
|
+
/**
|
|
23
|
+
* Generate a fingerprint hash from device data
|
|
24
|
+
* Uses SHA-256 hash of concatenated device attributes
|
|
25
|
+
*/
|
|
26
|
+
export function generateFingerprintHash(data) {
|
|
27
|
+
const components = [
|
|
28
|
+
data.ipAddress || '',
|
|
29
|
+
data.userAgent || '',
|
|
30
|
+
data.timezone || '',
|
|
31
|
+
data.language || '',
|
|
32
|
+
data.screenWidth?.toString() || '',
|
|
33
|
+
data.screenHeight?.toString() || '',
|
|
34
|
+
data.platform || '',
|
|
35
|
+
data.platformVersion || '',
|
|
36
|
+
];
|
|
37
|
+
const concatenated = components.join('|');
|
|
38
|
+
return crypto.createHash('sha256').update(concatenated).digest('hex');
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Normalize IP address for comparison
|
|
42
|
+
* Handles IPv4 and IPv6, removes subnet variations
|
|
43
|
+
*/
|
|
44
|
+
function normalizeIP(ip) {
|
|
45
|
+
if (!ip)
|
|
46
|
+
return '';
|
|
47
|
+
// For IPv4, use first 3 octets (e.g., 192.168.1.x)
|
|
48
|
+
if (ip.includes('.')) {
|
|
49
|
+
const parts = ip.split('.');
|
|
50
|
+
return parts.slice(0, 3).join('.');
|
|
51
|
+
}
|
|
52
|
+
// For IPv6, use first 4 groups (e.g., 2001:0db8:85a3:0000:xxxx)
|
|
53
|
+
if (ip.includes(':')) {
|
|
54
|
+
const parts = ip.split(':');
|
|
55
|
+
return parts.slice(0, 4).join(':');
|
|
56
|
+
}
|
|
57
|
+
return ip;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Normalize user agent for comparison
|
|
61
|
+
* Extracts key identifiers and removes version numbers
|
|
62
|
+
*/
|
|
63
|
+
function normalizeUserAgent(ua) {
|
|
64
|
+
if (!ua)
|
|
65
|
+
return '';
|
|
66
|
+
// Extract platform (iOS, Android, Windows, Mac, Linux)
|
|
67
|
+
const platformMatch = ua.match(/(iPhone|iPad|Android|Windows|Macintosh|Linux)/i);
|
|
68
|
+
const platform = platformMatch ? platformMatch[1] : '';
|
|
69
|
+
// Extract browser (Chrome, Safari, Firefox, Edge)
|
|
70
|
+
const browserMatch = ua.match(/(Chrome|Safari|Firefox|Edge|Opera)/i);
|
|
71
|
+
const browser = browserMatch ? browserMatch[1] : '';
|
|
72
|
+
return `${platform}|${browser}`.toLowerCase();
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Calculate confidence score by comparing two fingerprints
|
|
76
|
+
* Returns a score from 0-100 based on matched components
|
|
77
|
+
*/
|
|
78
|
+
export function calculateConfidenceScore(fingerprint1, fingerprint2) {
|
|
79
|
+
let score = 0;
|
|
80
|
+
const matchedFactors = [];
|
|
81
|
+
// Compare IP addresses (normalized to /24 subnet for IPv4)
|
|
82
|
+
if (fingerprint1.ipAddress && fingerprint2.ipAddress) {
|
|
83
|
+
const ip1 = normalizeIP(fingerprint1.ipAddress);
|
|
84
|
+
const ip2 = normalizeIP(fingerprint2.ipAddress);
|
|
85
|
+
if (ip1 === ip2) {
|
|
86
|
+
score += FINGERPRINT_WEIGHTS.IP_ADDRESS;
|
|
87
|
+
matchedFactors.push('ip');
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// Compare user agents (normalized to platform + browser)
|
|
91
|
+
if (fingerprint1.userAgent && fingerprint2.userAgent) {
|
|
92
|
+
const ua1 = normalizeUserAgent(fingerprint1.userAgent);
|
|
93
|
+
const ua2 = normalizeUserAgent(fingerprint2.userAgent);
|
|
94
|
+
if (ua1 === ua2) {
|
|
95
|
+
score += FINGERPRINT_WEIGHTS.USER_AGENT;
|
|
96
|
+
matchedFactors.push('user_agent');
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// Compare timezone
|
|
100
|
+
if (fingerprint1.timezone && fingerprint2.timezone) {
|
|
101
|
+
if (fingerprint1.timezone === fingerprint2.timezone) {
|
|
102
|
+
score += FINGERPRINT_WEIGHTS.TIMEZONE;
|
|
103
|
+
matchedFactors.push('timezone');
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
// Compare language
|
|
107
|
+
if (fingerprint1.language && fingerprint2.language) {
|
|
108
|
+
// Match first 2 characters (e.g., "en-US" matches "en-GB")
|
|
109
|
+
const lang1 = fingerprint1.language.substring(0, 2).toLowerCase();
|
|
110
|
+
const lang2 = fingerprint2.language.substring(0, 2).toLowerCase();
|
|
111
|
+
if (lang1 === lang2) {
|
|
112
|
+
score += FINGERPRINT_WEIGHTS.LANGUAGE;
|
|
113
|
+
matchedFactors.push('language');
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
// Compare screen resolution
|
|
117
|
+
if (fingerprint1.screenWidth &&
|
|
118
|
+
fingerprint1.screenHeight &&
|
|
119
|
+
fingerprint2.screenWidth &&
|
|
120
|
+
fingerprint2.screenHeight) {
|
|
121
|
+
if (fingerprint1.screenWidth === fingerprint2.screenWidth &&
|
|
122
|
+
fingerprint1.screenHeight === fingerprint2.screenHeight) {
|
|
123
|
+
score += FINGERPRINT_WEIGHTS.SCREEN_RESOLUTION;
|
|
124
|
+
matchedFactors.push('screen');
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return { score, matchedFactors };
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Match an install event to potential click events via probabilistic fingerprinting
|
|
131
|
+
* Returns the best match above confidence threshold within attribution window
|
|
132
|
+
*
|
|
133
|
+
* Note: Uses link-specific attribution windows - each link can have its own window
|
|
134
|
+
*/
|
|
135
|
+
export async function matchInstallToClick(installFingerprint, attributionWindowHours = DEFAULT_ATTRIBUTION_WINDOW_HOURS) {
|
|
136
|
+
// Query recent click events within maximum possible attribution window (90 days)
|
|
137
|
+
// We'll validate against each link's specific window during matching
|
|
138
|
+
const maxWindowHours = 2160; // 90 days
|
|
139
|
+
const cutoffTime = new Date(Date.now() - maxWindowHours * 60 * 60 * 1000);
|
|
140
|
+
const clicksResult = await db.query(`SELECT
|
|
141
|
+
ce.id as click_id,
|
|
142
|
+
ce.link_id,
|
|
143
|
+
ce.clicked_at,
|
|
144
|
+
l.attribution_window_hours,
|
|
145
|
+
df.ip_address,
|
|
146
|
+
df.user_agent,
|
|
147
|
+
df.timezone,
|
|
148
|
+
df.language,
|
|
149
|
+
df.screen_width,
|
|
150
|
+
df.screen_height,
|
|
151
|
+
df.platform,
|
|
152
|
+
df.platform_version
|
|
153
|
+
FROM click_events ce
|
|
154
|
+
INNER JOIN device_fingerprints df ON df.click_id = ce.id
|
|
155
|
+
INNER JOIN links l ON ce.link_id = l.id
|
|
156
|
+
WHERE ce.clicked_at >= $1
|
|
157
|
+
ORDER BY ce.clicked_at DESC
|
|
158
|
+
LIMIT 1000`, [cutoffTime]);
|
|
159
|
+
if (clicksResult.rows.length === 0) {
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
const installTime = new Date();
|
|
163
|
+
// Calculate confidence score for each potential match
|
|
164
|
+
let bestMatch = null;
|
|
165
|
+
let highestScore = 0;
|
|
166
|
+
for (const row of clicksResult.rows) {
|
|
167
|
+
// Check if click is within the link's specific attribution window
|
|
168
|
+
const linkWindowHours = row.attribution_window_hours || DEFAULT_ATTRIBUTION_WINDOW_HOURS;
|
|
169
|
+
const clickTime = new Date(row.clicked_at);
|
|
170
|
+
const timeDiffHours = (installTime.getTime() - clickTime.getTime()) / (1000 * 60 * 60);
|
|
171
|
+
if (timeDiffHours > linkWindowHours) {
|
|
172
|
+
// Click is too old for this link's attribution window, skip it
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
const clickFingerprint = {
|
|
176
|
+
ipAddress: row.ip_address,
|
|
177
|
+
userAgent: row.user_agent,
|
|
178
|
+
timezone: row.timezone,
|
|
179
|
+
language: row.language,
|
|
180
|
+
screenWidth: row.screen_width,
|
|
181
|
+
screenHeight: row.screen_height,
|
|
182
|
+
platform: row.platform,
|
|
183
|
+
platformVersion: row.platform_version,
|
|
184
|
+
};
|
|
185
|
+
const { score, matchedFactors } = calculateConfidenceScore(installFingerprint, clickFingerprint);
|
|
186
|
+
// Track the best match
|
|
187
|
+
if (score > highestScore && score >= CONFIDENCE_THRESHOLD) {
|
|
188
|
+
highestScore = score;
|
|
189
|
+
bestMatch = {
|
|
190
|
+
clickId: row.click_id,
|
|
191
|
+
linkId: row.link_id,
|
|
192
|
+
confidenceScore: score,
|
|
193
|
+
matchedFactors,
|
|
194
|
+
clickedAt: new Date(row.clicked_at),
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return bestMatch;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Store device fingerprint for a click event
|
|
202
|
+
*/
|
|
203
|
+
export async function storeFingerprintForClick(clickId, fingerprintData) {
|
|
204
|
+
const fingerprintHash = generateFingerprintHash(fingerprintData);
|
|
205
|
+
await db.query(`INSERT INTO device_fingerprints (
|
|
206
|
+
click_id,
|
|
207
|
+
fingerprint_hash,
|
|
208
|
+
ip_address,
|
|
209
|
+
user_agent,
|
|
210
|
+
timezone,
|
|
211
|
+
language,
|
|
212
|
+
screen_width,
|
|
213
|
+
screen_height,
|
|
214
|
+
platform,
|
|
215
|
+
platform_version
|
|
216
|
+
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`, [
|
|
217
|
+
clickId,
|
|
218
|
+
fingerprintHash,
|
|
219
|
+
fingerprintData.ipAddress,
|
|
220
|
+
fingerprintData.userAgent,
|
|
221
|
+
fingerprintData.timezone || null,
|
|
222
|
+
fingerprintData.language || null,
|
|
223
|
+
fingerprintData.screenWidth || null,
|
|
224
|
+
fingerprintData.screenHeight || null,
|
|
225
|
+
fingerprintData.platform || null,
|
|
226
|
+
fingerprintData.platformVersion || null,
|
|
227
|
+
]);
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Record an install event and attempt to match it to a click
|
|
231
|
+
*/
|
|
232
|
+
export async function recordInstallEvent(fingerprintData, deviceId, attributionWindowHours = DEFAULT_ATTRIBUTION_WINDOW_HOURS) {
|
|
233
|
+
const fingerprintHash = generateFingerprintHash(fingerprintData);
|
|
234
|
+
// Attempt to match install to a click
|
|
235
|
+
const match = await matchInstallToClick(fingerprintData, attributionWindowHours);
|
|
236
|
+
// Insert install event
|
|
237
|
+
const installResult = await db.query(`INSERT INTO install_events (
|
|
238
|
+
link_id,
|
|
239
|
+
click_id,
|
|
240
|
+
fingerprint_hash,
|
|
241
|
+
confidence_score,
|
|
242
|
+
installed_at,
|
|
243
|
+
first_open_at,
|
|
244
|
+
attribution_window_hours,
|
|
245
|
+
ip_address,
|
|
246
|
+
user_agent,
|
|
247
|
+
timezone,
|
|
248
|
+
language,
|
|
249
|
+
screen_width,
|
|
250
|
+
screen_height,
|
|
251
|
+
platform,
|
|
252
|
+
platform_version,
|
|
253
|
+
device_id,
|
|
254
|
+
deep_link_data
|
|
255
|
+
) VALUES ($1, $2, $3, $4, NOW(), NOW(), $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15)
|
|
256
|
+
RETURNING id, deep_link_data`, [
|
|
257
|
+
match?.linkId || null,
|
|
258
|
+
match?.clickId || null,
|
|
259
|
+
fingerprintHash,
|
|
260
|
+
match?.confidenceScore || null,
|
|
261
|
+
attributionWindowHours,
|
|
262
|
+
fingerprintData.ipAddress,
|
|
263
|
+
fingerprintData.userAgent,
|
|
264
|
+
fingerprintData.timezone || null,
|
|
265
|
+
fingerprintData.language || null,
|
|
266
|
+
fingerprintData.screenWidth || null,
|
|
267
|
+
fingerprintData.screenHeight || null,
|
|
268
|
+
fingerprintData.platform || null,
|
|
269
|
+
fingerprintData.platformVersion || null,
|
|
270
|
+
deviceId || null,
|
|
271
|
+
match ? JSON.stringify({}) : JSON.stringify({}), // Will be populated from link data
|
|
272
|
+
]);
|
|
273
|
+
const installId = installResult.rows[0].id;
|
|
274
|
+
let deepLinkData = {};
|
|
275
|
+
// If we have a match, retrieve the deep link data from the original link
|
|
276
|
+
if (match) {
|
|
277
|
+
const linkResult = await db.query(`SELECT
|
|
278
|
+
short_code,
|
|
279
|
+
original_url,
|
|
280
|
+
ios_url,
|
|
281
|
+
android_url,
|
|
282
|
+
web_fallback_url,
|
|
283
|
+
utm_parameters,
|
|
284
|
+
targeting_rules
|
|
285
|
+
FROM links
|
|
286
|
+
WHERE id = $1`, [match.linkId]);
|
|
287
|
+
if (linkResult.rows.length > 0) {
|
|
288
|
+
const link = linkResult.rows[0];
|
|
289
|
+
deepLinkData = {
|
|
290
|
+
shortCode: link.short_code,
|
|
291
|
+
originalUrl: link.original_url,
|
|
292
|
+
iosUrl: link.ios_url,
|
|
293
|
+
androidUrl: link.android_url,
|
|
294
|
+
webFallbackUrl: link.web_fallback_url,
|
|
295
|
+
utmParameters: link.utm_parameters,
|
|
296
|
+
targetingRules: link.targeting_rules,
|
|
297
|
+
clickedAt: match.clickedAt,
|
|
298
|
+
confidenceScore: match.confidenceScore,
|
|
299
|
+
matchedFactors: match.matchedFactors,
|
|
300
|
+
};
|
|
301
|
+
// Update the install event with deep link data
|
|
302
|
+
await db.query(`UPDATE install_events
|
|
303
|
+
SET deep_link_data = $1,
|
|
304
|
+
deep_link_retrieved = true
|
|
305
|
+
WHERE id = $2`, [JSON.stringify(deepLinkData), installId]);
|
|
306
|
+
// Trigger webhooks for install_event (only if attributed)
|
|
307
|
+
try {
|
|
308
|
+
// Get the link's user_id for webhook lookup
|
|
309
|
+
const linkUserResult = await db.query('SELECT user_id FROM links WHERE id = $1', [match.linkId]);
|
|
310
|
+
if (linkUserResult.rows.length > 0) {
|
|
311
|
+
const userId = linkUserResult.rows[0].user_id;
|
|
312
|
+
const webhooksResult = await db.query('SELECT * FROM webhooks WHERE user_id = $1 AND is_active = true', [userId]);
|
|
313
|
+
if (webhooksResult.rows.length > 0) {
|
|
314
|
+
const { triggerWebhooks } = await import('./webhook.js');
|
|
315
|
+
const installEventData = {
|
|
316
|
+
id: installId,
|
|
317
|
+
linkId: match.linkId,
|
|
318
|
+
fingerprintHash,
|
|
319
|
+
confidenceScore: match.confidenceScore,
|
|
320
|
+
installedAt: new Date().toISOString(),
|
|
321
|
+
deepLinkData,
|
|
322
|
+
ipAddress: fingerprintData.ipAddress,
|
|
323
|
+
userAgent: fingerprintData.userAgent,
|
|
324
|
+
platform: fingerprintData.platform,
|
|
325
|
+
};
|
|
326
|
+
// Trigger webhooks without delivery logging (basic version)
|
|
327
|
+
// For delivery logging, use @linkforty/cloud premium features
|
|
328
|
+
await triggerWebhooks(webhooksResult.rows, 'install_event', installId, installEventData);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
catch (webhookError) {
|
|
333
|
+
console.error(`Error triggering install webhooks: ${webhookError}`);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
return {
|
|
338
|
+
installId,
|
|
339
|
+
match,
|
|
340
|
+
deepLinkData,
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
//# sourceMappingURL=fingerprint.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fingerprint.js","sourceRoot":"","sources":["../../src/lib/fingerprint.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/utils.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ export declare function generateShortCode(length?: number): string;
|
|
|
2
2
|
export declare function parseUserAgent(userAgent: string): {
|
|
3
3
|
deviceType: string;
|
|
4
4
|
platform: string;
|
|
5
|
+
platformVersion: string | undefined;
|
|
5
6
|
browser: string;
|
|
6
7
|
};
|
|
7
8
|
export declare function getLocationFromIP(ip: string): {
|
package/dist/lib/utils.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/lib/utils.ts"],"names":[],"mappings":"AAIA,wBAAgB,iBAAiB,CAAC,MAAM,GAAE,MAAU,GAAG,MAAM,CAE5D;AAED,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/lib/utils.ts"],"names":[],"mappings":"AAIA,wBAAgB,iBAAiB,CAAC,MAAM,GAAE,MAAU,GAAG,MAAM,CAE5D;AAED,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM;;;;;EAU/C;AAoDD,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,MAAM;;;;;;;;;;;;;;;;EAwB3C;AAED,wBAAgB,gBAAgB,CAC9B,WAAW,EAAE,MAAM,EACnB,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GACrC,MAAM,CAYR;AAED,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,KAAK,GAAG,SAAS,GAAG,KAAK,CAYzE"}
|
package/dist/lib/utils.js
CHANGED
|
@@ -1,25 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
exports.generateShortCode = generateShortCode;
|
|
7
|
-
exports.parseUserAgent = parseUserAgent;
|
|
8
|
-
exports.getLocationFromIP = getLocationFromIP;
|
|
9
|
-
exports.buildRedirectUrl = buildRedirectUrl;
|
|
10
|
-
exports.detectDevice = detectDevice;
|
|
11
|
-
const nanoid_1 = require("nanoid");
|
|
12
|
-
const geoip_lite_1 = __importDefault(require("geoip-lite"));
|
|
13
|
-
const ua_parser_js_1 = __importDefault(require("ua-parser-js"));
|
|
14
|
-
function generateShortCode(length = 8) {
|
|
15
|
-
return (0, nanoid_1.nanoid)(length);
|
|
1
|
+
import { nanoid } from 'nanoid';
|
|
2
|
+
import geoip from 'geoip-lite';
|
|
3
|
+
import UAParser from 'ua-parser-js';
|
|
4
|
+
export function generateShortCode(length = 8) {
|
|
5
|
+
return nanoid(length);
|
|
16
6
|
}
|
|
17
|
-
function parseUserAgent(userAgent) {
|
|
18
|
-
const parser = new
|
|
7
|
+
export function parseUserAgent(userAgent) {
|
|
8
|
+
const parser = new UAParser(userAgent);
|
|
19
9
|
const result = parser.getResult();
|
|
20
10
|
return {
|
|
21
11
|
deviceType: result.device.type || 'desktop',
|
|
22
12
|
platform: result.os.name || 'unknown',
|
|
13
|
+
platformVersion: result.os.version || undefined,
|
|
23
14
|
browser: result.browser.name || 'unknown',
|
|
24
15
|
};
|
|
25
16
|
}
|
|
@@ -72,8 +63,8 @@ const COUNTRY_NAMES = {
|
|
|
72
63
|
HU: 'Hungary',
|
|
73
64
|
RO: 'Romania',
|
|
74
65
|
};
|
|
75
|
-
function getLocationFromIP(ip) {
|
|
76
|
-
const geo =
|
|
66
|
+
export function getLocationFromIP(ip) {
|
|
67
|
+
const geo = geoip.lookup(ip);
|
|
77
68
|
if (!geo) {
|
|
78
69
|
return {
|
|
79
70
|
countryCode: null,
|
|
@@ -95,7 +86,7 @@ function getLocationFromIP(ip) {
|
|
|
95
86
|
timezone: geo.timezone,
|
|
96
87
|
};
|
|
97
88
|
}
|
|
98
|
-
function buildRedirectUrl(originalUrl, utmParameters) {
|
|
89
|
+
export function buildRedirectUrl(originalUrl, utmParameters) {
|
|
99
90
|
const url = new URL(originalUrl);
|
|
100
91
|
if (utmParameters) {
|
|
101
92
|
Object.entries(utmParameters).forEach(([key, value]) => {
|
|
@@ -106,7 +97,7 @@ function buildRedirectUrl(originalUrl, utmParameters) {
|
|
|
106
97
|
}
|
|
107
98
|
return url.toString();
|
|
108
99
|
}
|
|
109
|
-
function detectDevice(userAgent) {
|
|
100
|
+
export function detectDevice(userAgent) {
|
|
110
101
|
const ua = userAgent.toLowerCase();
|
|
111
102
|
if (ua.includes('iphone') || ua.includes('ipad') || ua.includes('ipod')) {
|
|
112
103
|
return 'ios';
|
package/dist/lib/utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/lib/utils.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/lib/utils.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Webhook, WebhookPayload, WebhookDeliveryResult, WebhookEvent } from '../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Generate HMAC SHA-256 signature for webhook payload
|
|
4
|
+
*/
|
|
5
|
+
export declare function generateWebhookSignature(payload: string, secret: string): string;
|
|
6
|
+
/**
|
|
7
|
+
* Generate a secure random secret for webhook signing
|
|
8
|
+
*/
|
|
9
|
+
export declare function generateWebhookSecret(): string;
|
|
10
|
+
/**
|
|
11
|
+
* Deliver webhook with retry logic and exponential backoff
|
|
12
|
+
*/
|
|
13
|
+
export declare function deliverWebhook(webhook: Webhook, payload: WebhookPayload, logDelivery?: (result: WebhookDeliveryResult) => Promise<void>): Promise<WebhookDeliveryResult>;
|
|
14
|
+
/**
|
|
15
|
+
* Trigger webhooks for a specific event (fire and forget)
|
|
16
|
+
*/
|
|
17
|
+
export declare function triggerWebhooks(webhooks: Webhook[], event: WebhookEvent, eventId: string, data: any, logDelivery?: (webhookId: string, result: WebhookDeliveryResult) => Promise<void>): Promise<void>;
|
|
18
|
+
//# sourceMappingURL=webhook.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook.d.ts","sourceRoot":"","sources":["../../src/lib/webhook.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,qBAAqB,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEtG;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAEhF;AAED;;GAEG;AACH,wBAAgB,qBAAqB,IAAI,MAAM,CAE9C;AA4ED;;GAEG;AACH,wBAAsB,cAAc,CAClC,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,cAAc,EACvB,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,qBAAqB,KAAK,OAAO,CAAC,IAAI,CAAC,GAC7D,OAAO,CAAC,qBAAqB,CAAC,CAmChC;AAED;;GAEG;AACH,wBAAsB,eAAe,CACnC,QAAQ,EAAE,OAAO,EAAE,EACnB,KAAK,EAAE,YAAY,EACnB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,GAAG,EACT,WAAW,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,qBAAqB,KAAK,OAAO,CAAC,IAAI,CAAC,GAChF,OAAO,CAAC,IAAI,CAAC,CAuCf"}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import crypto from 'crypto';
|
|
2
|
+
/**
|
|
3
|
+
* Generate HMAC SHA-256 signature for webhook payload
|
|
4
|
+
*/
|
|
5
|
+
export function generateWebhookSignature(payload, secret) {
|
|
6
|
+
return crypto.createHmac('sha256', secret).update(payload).digest('hex');
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Generate a secure random secret for webhook signing
|
|
10
|
+
*/
|
|
11
|
+
export function generateWebhookSecret() {
|
|
12
|
+
return crypto.randomBytes(32).toString('hex');
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Sleep utility for retry delays
|
|
16
|
+
*/
|
|
17
|
+
function sleep(ms) {
|
|
18
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Attempt a single webhook delivery
|
|
22
|
+
*/
|
|
23
|
+
async function attemptWebhookDelivery(webhook, payload, attemptNumber) {
|
|
24
|
+
const payloadString = JSON.stringify(payload);
|
|
25
|
+
const signature = generateWebhookSignature(payloadString, webhook.secret);
|
|
26
|
+
const headers = {
|
|
27
|
+
'Content-Type': 'application/json',
|
|
28
|
+
'X-LinkForty-Signature': `sha256=${signature}`,
|
|
29
|
+
'X-LinkForty-Event': payload.event,
|
|
30
|
+
'X-LinkForty-Event-ID': payload.event_id,
|
|
31
|
+
'User-Agent': 'LinkForty-Webhook/1.0',
|
|
32
|
+
...webhook.headers,
|
|
33
|
+
};
|
|
34
|
+
const controller = new AbortController();
|
|
35
|
+
const timeoutId = setTimeout(() => controller.abort(), webhook.timeout_ms);
|
|
36
|
+
try {
|
|
37
|
+
const response = await fetch(webhook.url, {
|
|
38
|
+
method: 'POST',
|
|
39
|
+
headers,
|
|
40
|
+
body: payloadString,
|
|
41
|
+
signal: controller.signal,
|
|
42
|
+
});
|
|
43
|
+
clearTimeout(timeoutId);
|
|
44
|
+
const responseBody = await response.text().catch(() => '');
|
|
45
|
+
const result = {
|
|
46
|
+
success: response.ok,
|
|
47
|
+
webhookId: webhook.id,
|
|
48
|
+
eventType: payload.event,
|
|
49
|
+
eventId: payload.event_id,
|
|
50
|
+
responseStatus: response.status,
|
|
51
|
+
responseBody: responseBody.substring(0, 1000), // Limit response body size
|
|
52
|
+
attemptNumber,
|
|
53
|
+
deliveredAt: response.ok ? new Date().toISOString() : undefined,
|
|
54
|
+
};
|
|
55
|
+
if (!response.ok) {
|
|
56
|
+
result.errorMessage = `HTTP ${response.status}: ${response.statusText}`;
|
|
57
|
+
}
|
|
58
|
+
return result;
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
clearTimeout(timeoutId);
|
|
62
|
+
return {
|
|
63
|
+
success: false,
|
|
64
|
+
webhookId: webhook.id,
|
|
65
|
+
eventType: payload.event,
|
|
66
|
+
eventId: payload.event_id,
|
|
67
|
+
errorMessage: error.name === 'AbortError'
|
|
68
|
+
? `Timeout after ${webhook.timeout_ms}ms`
|
|
69
|
+
: error.message || 'Unknown error',
|
|
70
|
+
attemptNumber,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Deliver webhook with retry logic and exponential backoff
|
|
76
|
+
*/
|
|
77
|
+
export async function deliverWebhook(webhook, payload, logDelivery) {
|
|
78
|
+
const maxRetries = webhook.retry_count || 3;
|
|
79
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
80
|
+
const result = await attemptWebhookDelivery(webhook, payload, attempt);
|
|
81
|
+
// Log delivery attempt if logging function provided
|
|
82
|
+
if (logDelivery) {
|
|
83
|
+
await logDelivery(result).catch(err => {
|
|
84
|
+
console.error('Failed to log webhook delivery:', err);
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
// If successful, return immediately
|
|
88
|
+
if (result.success) {
|
|
89
|
+
return result;
|
|
90
|
+
}
|
|
91
|
+
// If not the last attempt, wait before retrying with exponential backoff
|
|
92
|
+
if (attempt < maxRetries) {
|
|
93
|
+
// Exponential backoff: 1s, 2s, 4s, 8s, 16s, 30s (capped at 30s)
|
|
94
|
+
const delayMs = Math.min(1000 * Math.pow(2, attempt - 1), 30000);
|
|
95
|
+
await sleep(delayMs);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// Return the last failed result
|
|
99
|
+
return {
|
|
100
|
+
success: false,
|
|
101
|
+
webhookId: webhook.id,
|
|
102
|
+
eventType: payload.event,
|
|
103
|
+
eventId: payload.event_id,
|
|
104
|
+
errorMessage: `Failed after ${maxRetries} attempts`,
|
|
105
|
+
attemptNumber: maxRetries,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Trigger webhooks for a specific event (fire and forget)
|
|
110
|
+
*/
|
|
111
|
+
export async function triggerWebhooks(webhooks, event, eventId, data, logDelivery) {
|
|
112
|
+
// Create webhook payload
|
|
113
|
+
const payload = {
|
|
114
|
+
event,
|
|
115
|
+
event_id: eventId,
|
|
116
|
+
timestamp: new Date().toISOString(),
|
|
117
|
+
data,
|
|
118
|
+
};
|
|
119
|
+
// Filter webhooks that should receive this event
|
|
120
|
+
const relevantWebhooks = webhooks.filter(webhook => webhook.is_active && webhook.events.includes(event));
|
|
121
|
+
if (relevantWebhooks.length === 0) {
|
|
122
|
+
return; // No webhooks to trigger
|
|
123
|
+
}
|
|
124
|
+
// Fire and forget - don't await these deliveries
|
|
125
|
+
const deliveryPromises = relevantWebhooks.map(async (webhook) => {
|
|
126
|
+
try {
|
|
127
|
+
const result = await deliverWebhook(webhook, payload, logDelivery ? (result) => logDelivery(webhook.id, result) : undefined);
|
|
128
|
+
if (!result.success) {
|
|
129
|
+
console.error(`Webhook ${webhook.id} delivery failed:`, result.errorMessage);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
console.error(`Webhook ${webhook.id} delivery error:`, error);
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
// Fire and forget - log errors but don't block
|
|
137
|
+
Promise.all(deliveryPromises).catch(err => {
|
|
138
|
+
console.error('Webhook delivery batch error:', err);
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=webhook.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook.js","sourceRoot":"","sources":["../../src/lib/webhook.ts"],"names":[],"mappings":"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"}
|