@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.
Files changed (61) hide show
  1. package/dist/index.d.ts +4 -1
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +26 -43
  4. package/dist/index.js.map +1 -1
  5. package/dist/lib/database.d.ts.map +1 -1
  6. package/dist/lib/database.js +160 -12
  7. package/dist/lib/database.js.map +1 -1
  8. package/dist/lib/event-emitter.d.ts +46 -0
  9. package/dist/lib/event-emitter.d.ts.map +1 -0
  10. package/dist/lib/event-emitter.js +24 -0
  11. package/dist/lib/event-emitter.js.map +1 -0
  12. package/dist/lib/fingerprint.d.ts +64 -0
  13. package/dist/lib/fingerprint.d.ts.map +1 -0
  14. package/dist/lib/fingerprint.js +343 -0
  15. package/dist/lib/fingerprint.js.map +1 -0
  16. package/dist/lib/utils.d.ts +1 -0
  17. package/dist/lib/utils.d.ts.map +1 -1
  18. package/dist/lib/utils.js +12 -21
  19. package/dist/lib/utils.js.map +1 -1
  20. package/dist/lib/webhook.d.ts +18 -0
  21. package/dist/lib/webhook.d.ts.map +1 -0
  22. package/dist/lib/webhook.js +141 -0
  23. package/dist/lib/webhook.js.map +1 -0
  24. package/dist/routes/analytics.js +14 -17
  25. package/dist/routes/analytics.js.map +1 -1
  26. package/dist/routes/debug.d.ts +7 -0
  27. package/dist/routes/debug.d.ts.map +1 -0
  28. package/dist/routes/debug.js +318 -0
  29. package/dist/routes/debug.js.map +1 -0
  30. package/dist/routes/index.d.ts +5 -0
  31. package/dist/routes/index.d.ts.map +1 -1
  32. package/dist/routes/index.js +8 -9
  33. package/dist/routes/index.js.map +1 -1
  34. package/dist/routes/links.d.ts.map +1 -1
  35. package/dist/routes/links.js +53 -38
  36. package/dist/routes/links.js.map +1 -1
  37. package/dist/routes/preview.d.ts +3 -0
  38. package/dist/routes/preview.d.ts.map +1 -0
  39. package/dist/routes/preview.js +222 -0
  40. package/dist/routes/preview.js.map +1 -0
  41. package/dist/routes/qr.d.ts +6 -0
  42. package/dist/routes/qr.d.ts.map +1 -0
  43. package/dist/routes/qr.js +130 -0
  44. package/dist/routes/qr.js.map +1 -0
  45. package/dist/routes/redirect.d.ts.map +1 -1
  46. package/dist/routes/redirect.js +142 -22
  47. package/dist/routes/redirect.js.map +1 -1
  48. package/dist/routes/sdk.d.ts +7 -0
  49. package/dist/routes/sdk.d.ts.map +1 -0
  50. package/dist/routes/sdk.js +262 -0
  51. package/dist/routes/sdk.js.map +1 -0
  52. package/dist/routes/webhooks.d.ts +3 -0
  53. package/dist/routes/webhooks.d.ts.map +1 -0
  54. package/dist/routes/webhooks.js +176 -0
  55. package/dist/routes/webhooks.js.map +1 -0
  56. package/dist/scripts/migrate.js +2 -4
  57. package/dist/scripts/migrate.js.map +1 -1
  58. package/dist/types/index.d.ts +81 -0
  59. package/dist/types/index.d.ts.map +1 -1
  60. package/dist/types/index.js +1 -2
  61. package/package.json +11 -7
@@ -1,15 +1,14 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.redirectRoutes = redirectRoutes;
4
- const database_js_1 = require("../lib/database.js");
5
- const utils_js_1 = require("../lib/utils.js");
6
- async function redirectRoutes(fastify) {
7
- // Handle short link redirects
8
- fastify.get('/:shortCode', async (request, reply) => {
9
- const { shortCode } = request.params;
1
+ import { db } from '../lib/database.js';
2
+ import { parseUserAgent, getLocationFromIP, buildRedirectUrl, detectDevice } from '../lib/utils.js';
3
+ import { storeFingerprintForClick } from '../lib/fingerprint.js';
4
+ import { emitClickEvent } from '../lib/event-emitter.js';
5
+ export async function redirectRoutes(fastify) {
6
+ // Helper function to handle the actual redirect logic
7
+ async function handleRedirect(request, reply, shortCode, templateSlug) {
10
8
  let linkData = null;
9
+ // Build cache key (include template if present)
10
+ const cacheKey = templateSlug ? `link:${templateSlug}:${shortCode}` : `link:${shortCode}`;
11
11
  // Try to get link from cache if Redis is available
12
- const cacheKey = `link:${shortCode}`;
13
12
  if (fastify.redis) {
14
13
  try {
15
14
  linkData = await fastify.redis.get(cacheKey);
@@ -19,10 +18,30 @@ async function redirectRoutes(fastify) {
19
18
  }
20
19
  }
21
20
  if (!linkData) {
22
- // Get from database
23
- const result = await database_js_1.db.query(`SELECT * FROM links
24
- WHERE short_code = $1 AND is_active = true
25
- AND (expires_at IS NULL OR expires_at > NOW())`, [shortCode]);
21
+ // Build query based on whether template slug is provided
22
+ let query;
23
+ let params;
24
+ if (templateSlug) {
25
+ // Template-based URL: verify both template and link match
26
+ query = `
27
+ SELECT l.* FROM links l
28
+ LEFT JOIN link_templates t ON l.template_id = t.id
29
+ WHERE l.short_code = $1 AND t.slug = $2
30
+ AND l.is_active = true
31
+ AND (l.expires_at IS NULL OR l.expires_at > NOW())
32
+ `;
33
+ params = [shortCode, templateSlug];
34
+ }
35
+ else {
36
+ // Legacy URL: just lookup by short code
37
+ query = `
38
+ SELECT * FROM links
39
+ WHERE short_code = $1 AND is_active = true
40
+ AND (expires_at IS NULL OR expires_at > NOW())
41
+ `;
42
+ params = [shortCode];
43
+ }
44
+ const result = await db.query(query, params);
26
45
  if (result.rows.length === 0) {
27
46
  return reply.status(404).send({ error: 'Link not found' });
28
47
  }
@@ -44,8 +63,8 @@ async function redirectRoutes(fastify) {
44
63
  const ip = request.ip;
45
64
  const acceptLanguage = request.headers['accept-language'] || '';
46
65
  // Get user's actual data for targeting checks
47
- const device = (0, utils_js_1.detectDevice)(userAgent);
48
- const { countryCode } = (0, utils_js_1.getLocationFromIP)(ip);
66
+ const device = detectDevice(userAgent);
67
+ const { countryCode } = getLocationFromIP(ip);
49
68
  // Extract primary language from accept-language header (e.g., "en-US,en;q=0.9" -> "en")
50
69
  const primaryLanguage = acceptLanguage.split(',')[0]?.split('-')[0]?.toLowerCase();
51
70
  const rules = link.targeting_rules;
@@ -81,18 +100,27 @@ async function redirectRoutes(fastify) {
81
100
  const userAgent = request.headers['user-agent'] || '';
82
101
  const ip = request.ip;
83
102
  const referrer = request.headers.referer || null;
84
- const { deviceType, platform } = (0, utils_js_1.parseUserAgent)(userAgent);
85
- const { countryCode, countryName, region, city, latitude, longitude, timezone } = (0, utils_js_1.getLocationFromIP)(ip);
103
+ const acceptLanguage = request.headers['accept-language'] || '';
104
+ const deviceType = detectDevice(userAgent);
105
+ const { platform, platformVersion } = parseUserAgent(userAgent);
106
+ const { countryCode, countryName, region, city, latitude, longitude, timezone } = getLocationFromIP(ip);
86
107
  // Extract UTM parameters from query string
87
108
  const query = request.query;
88
109
  const utmSource = query?.utm_source;
89
110
  const utmMedium = query?.utm_medium;
90
111
  const utmCampaign = query?.utm_campaign;
91
- await database_js_1.db.query(`INSERT INTO click_events (
112
+ // Extract fingerprint data from query params (sent by SDK/client)
113
+ const fpTimezone = query?.fp_tz || timezone || undefined;
114
+ const fpLanguage = query?.fp_lang || acceptLanguage.split(',')[0]?.split(';')[0] || undefined;
115
+ const fpScreenWidth = query?.fp_sw ? parseInt(query.fp_sw, 10) : undefined;
116
+ const fpScreenHeight = query?.fp_sh ? parseInt(query.fp_sh, 10) : undefined;
117
+ // Insert click event
118
+ const clickResult = await db.query(`INSERT INTO click_events (
92
119
  link_id, ip_address, user_agent, device_type, platform,
93
120
  country_code, country_name, region, city, latitude, longitude, timezone,
94
121
  utm_source, utm_medium, utm_campaign, referrer
95
- ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16)`, [
122
+ ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16)
123
+ RETURNING id`, [
96
124
  link.id,
97
125
  ip,
98
126
  userAgent,
@@ -110,6 +138,88 @@ async function redirectRoutes(fastify) {
110
138
  utmCampaign,
111
139
  referrer,
112
140
  ]);
141
+ const clickId = clickResult.rows[0].id;
142
+ // Store device fingerprint for deferred deep linking
143
+ const fingerprintData = {
144
+ ipAddress: ip,
145
+ userAgent,
146
+ timezone: fpTimezone,
147
+ language: fpLanguage,
148
+ screenWidth: fpScreenWidth,
149
+ screenHeight: fpScreenHeight,
150
+ platform: deviceType,
151
+ platformVersion,
152
+ };
153
+ await storeFingerprintForClick(clickId, fingerprintData);
154
+ // Determine redirect URL for event emission
155
+ let redirectUrl = link.original_url;
156
+ let redirectReason = 'original_url';
157
+ if (deviceType === 'ios' && link.ios_url) {
158
+ redirectUrl = link.ios_url;
159
+ redirectReason = 'ios_url';
160
+ }
161
+ else if (deviceType === 'android' && link.android_url) {
162
+ redirectUrl = link.android_url;
163
+ redirectReason = 'android_url';
164
+ }
165
+ else if (deviceType === 'web' && link.web_fallback_url) {
166
+ redirectUrl = link.web_fallback_url;
167
+ redirectReason = 'web_fallback_url';
168
+ }
169
+ const finalRedirectUrl = buildRedirectUrl(redirectUrl, link.utm_parameters);
170
+ // Emit click event for real-time streaming to WebSocket clients
171
+ emitClickEvent({
172
+ eventId: clickId,
173
+ timestamp: new Date().toISOString(),
174
+ linkId: link.id,
175
+ shortCode: link.short_code,
176
+ userId: link.user_id,
177
+ ipAddress: ip,
178
+ userAgent,
179
+ country: countryCode || undefined,
180
+ city: city || undefined,
181
+ deviceType,
182
+ platform: platform || undefined,
183
+ redirectUrl: finalRedirectUrl,
184
+ redirectReason,
185
+ targetingMatched: true, // If we got here, targeting matched
186
+ utmParameters: link.utm_parameters || undefined,
187
+ referer: referrer || undefined,
188
+ language: fpLanguage,
189
+ });
190
+ // Trigger webhooks for click_event
191
+ try {
192
+ const webhooksResult = await db.query('SELECT * FROM webhooks WHERE user_id = $1 AND is_active = true', [link.user_id]);
193
+ if (webhooksResult.rows.length > 0) {
194
+ const { triggerWebhooks } = await import('../lib/webhook.js');
195
+ const clickEventData = {
196
+ id: clickId,
197
+ linkId: link.id,
198
+ clickedAt: new Date().toISOString(),
199
+ ipAddress: ip,
200
+ userAgent,
201
+ deviceType,
202
+ platform,
203
+ countryCode,
204
+ countryName,
205
+ region,
206
+ city,
207
+ latitude,
208
+ longitude,
209
+ timezone,
210
+ utmSource,
211
+ utmMedium,
212
+ utmCampaign,
213
+ referrer,
214
+ };
215
+ // Trigger webhooks without delivery logging (basic version)
216
+ // For delivery logging, use @linkforty/cloud premium features
217
+ await triggerWebhooks(webhooksResult.rows, 'click_event', clickId, clickEventData);
218
+ }
219
+ }
220
+ catch (webhookError) {
221
+ fastify.log.error(`Error triggering click webhooks: ${webhookError}`);
222
+ }
113
223
  }
114
224
  catch (error) {
115
225
  fastify.log.error(`Error tracking click: ${error}`);
@@ -117,7 +227,7 @@ async function redirectRoutes(fastify) {
117
227
  });
118
228
  // Determine redirect URL based on device
119
229
  const userAgent = request.headers['user-agent'] || '';
120
- const device = (0, utils_js_1.detectDevice)(userAgent);
230
+ const device = detectDevice(userAgent);
121
231
  let redirectUrl = link.original_url;
122
232
  // Check for device-specific URLs
123
233
  if (device === 'ios' && link.ios_url) {
@@ -130,9 +240,19 @@ async function redirectRoutes(fastify) {
130
240
  redirectUrl = link.web_fallback_url;
131
241
  }
132
242
  // Add UTM parameters
133
- const finalUrl = (0, utils_js_1.buildRedirectUrl)(redirectUrl, link.utm_parameters);
243
+ const finalUrl = buildRedirectUrl(redirectUrl, link.utm_parameters);
134
244
  // Redirect
135
245
  return reply.redirect(302, finalUrl);
246
+ }
247
+ // Template-based shortlink route: /:templateSlug/:shortCode
248
+ fastify.get('/:templateSlug/:shortCode', async (request, reply) => {
249
+ const { templateSlug, shortCode } = request.params;
250
+ return handleRedirect(request, reply, shortCode, templateSlug);
251
+ });
252
+ // Legacy shortlink route (no template): /:shortCode
253
+ fastify.get('/:shortCode', async (request, reply) => {
254
+ const { shortCode } = request.params;
255
+ return handleRedirect(request, reply, shortCode);
136
256
  });
137
257
  }
138
258
  //# sourceMappingURL=redirect.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"redirect.js","sourceRoot":"","sources":["../../src/routes/redirect.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"redirect.js","sourceRoot":"","sources":["../../src/routes/redirect.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import { FastifyInstance } from 'fastify';
2
+ /**
3
+ * SDK Routes - Mobile SDK endpoints for deferred deep linking
4
+ * These endpoints are used by the mobile SDKs to report installs and retrieve attribution data
5
+ */
6
+ export declare function sdkRoutes(fastify: FastifyInstance): Promise<void>;
7
+ //# sourceMappingURL=sdk.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sdk.d.ts","sourceRoot":"","sources":["../../src/routes/sdk.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAU1C;;;GAGG;AACH,wBAAsB,SAAS,CAAC,OAAO,EAAE,eAAe,iBAySvD"}
@@ -0,0 +1,262 @@
1
+ import { z } from 'zod';
2
+ import { db } from '../lib/database.js';
3
+ import { recordInstallEvent, } from '../lib/fingerprint.js';
4
+ import { triggerWebhooks } from '../lib/webhook.js';
5
+ /**
6
+ * SDK Routes - Mobile SDK endpoints for deferred deep linking
7
+ * These endpoints are used by the mobile SDKs to report installs and retrieve attribution data
8
+ */
9
+ export async function sdkRoutes(fastify) {
10
+ /**
11
+ * POST /api/sdk/v1/install
12
+ * Report app installation and retrieve deferred deep link data
13
+ *
14
+ * Request body:
15
+ * - ipAddress: Client IP (auto-detected if not provided)
16
+ * - userAgent: Client user agent
17
+ * - timezone: Device timezone (e.g., "America/New_York")
18
+ * - language: Device language (e.g., "en-US")
19
+ * - screenWidth: Screen width in pixels
20
+ * - screenHeight: Screen height in pixels
21
+ * - platform: Platform name (e.g., "iOS", "Android")
22
+ * - platformVersion: Platform version (e.g., "15.0")
23
+ * - deviceId: Optional device identifier (IDFA, GAID, etc.)
24
+ * - attributionWindowHours: Optional custom attribution window (default: 168 = 7 days)
25
+ *
26
+ * Response:
27
+ * - installId: UUID of the install event
28
+ * - attributed: Boolean indicating if install was matched to a click
29
+ * - confidenceScore: Confidence score (0-100) if matched
30
+ * - matchedFactors: Array of matched fingerprint factors
31
+ * - deepLinkData: Deep link data if matched (shortCode, URLs, UTM params, etc.)
32
+ */
33
+ fastify.post('/api/sdk/v1/install', async (request, reply) => {
34
+ const schema = z.object({
35
+ ipAddress: z.string().optional(),
36
+ userAgent: z.string(),
37
+ timezone: z.string().optional(),
38
+ language: z.string().optional(),
39
+ screenWidth: z.number().optional(),
40
+ screenHeight: z.number().optional(),
41
+ platform: z.string().optional(),
42
+ platformVersion: z.string().optional(),
43
+ deviceId: z.string().optional(),
44
+ attributionWindowHours: z.number().optional(),
45
+ });
46
+ const body = schema.parse(request.body);
47
+ // Use client-provided IP or fallback to request IP
48
+ const ipAddress = body.ipAddress || request.ip;
49
+ const fingerprintData = {
50
+ ipAddress,
51
+ userAgent: body.userAgent,
52
+ timezone: body.timezone,
53
+ language: body.language,
54
+ screenWidth: body.screenWidth,
55
+ screenHeight: body.screenHeight,
56
+ platform: body.platform,
57
+ platformVersion: body.platformVersion,
58
+ };
59
+ try {
60
+ const result = await recordInstallEvent(fingerprintData, body.deviceId, body.attributionWindowHours);
61
+ return reply.status(200).send({
62
+ installId: result.installId,
63
+ attributed: result.match !== null,
64
+ confidenceScore: result.match?.confidenceScore || 0,
65
+ matchedFactors: result.match?.matchedFactors || [],
66
+ deepLinkData: result.deepLinkData,
67
+ });
68
+ }
69
+ catch (error) {
70
+ fastify.log.error(`Error recording install event: ${error}`);
71
+ return reply.status(500).send({
72
+ error: 'Failed to record install event',
73
+ message: error.message,
74
+ });
75
+ }
76
+ });
77
+ /**
78
+ * GET /api/sdk/v1/attribution/:fingerprint
79
+ * Retrieve attribution data for a specific device fingerprint
80
+ * Used for debugging or delayed attribution lookups
81
+ *
82
+ * Response:
83
+ * - fingerprint: The fingerprint hash
84
+ * - attributed: Boolean indicating if attributed to a click
85
+ * - installEvent: Install event data if found
86
+ * - clickEvent: Matched click event data if attributed
87
+ * - linkData: Link data if attributed
88
+ */
89
+ fastify.get('/api/sdk/v1/attribution/:fingerprint', async (request, reply) => {
90
+ const { fingerprint } = request.params;
91
+ try {
92
+ // Look up install event by fingerprint
93
+ const installResult = await db.query(`SELECT
94
+ ie.*,
95
+ l.short_code,
96
+ l.original_url,
97
+ l.ios_url,
98
+ l.android_url,
99
+ l.web_fallback_url,
100
+ l.utm_parameters
101
+ FROM install_events ie
102
+ LEFT JOIN links l ON ie.link_id = l.id
103
+ WHERE ie.fingerprint_hash = $1
104
+ ORDER BY ie.installed_at DESC
105
+ LIMIT 1`, [fingerprint]);
106
+ if (installResult.rows.length === 0) {
107
+ return reply.status(404).send({
108
+ error: 'No install event found for this fingerprint',
109
+ });
110
+ }
111
+ const install = installResult.rows[0];
112
+ const attributed = install.link_id !== null;
113
+ let clickData = null;
114
+ if (install.click_id) {
115
+ const clickResult = await db.query(`SELECT * FROM click_events WHERE id = $1`, [install.click_id]);
116
+ if (clickResult.rows.length > 0) {
117
+ clickData = clickResult.rows[0];
118
+ }
119
+ }
120
+ return reply.status(200).send({
121
+ fingerprint,
122
+ attributed,
123
+ installEvent: {
124
+ id: install.id,
125
+ installedAt: install.installed_at,
126
+ firstOpenAt: install.first_open_at,
127
+ confidenceScore: parseFloat(install.confidence_score || '0'),
128
+ deepLinkRetrieved: install.deep_link_retrieved,
129
+ },
130
+ clickEvent: clickData
131
+ ? {
132
+ id: clickData.id,
133
+ clickedAt: clickData.clicked_at,
134
+ deviceType: clickData.device_type,
135
+ platform: clickData.platform,
136
+ countryCode: clickData.country_code,
137
+ city: clickData.city,
138
+ }
139
+ : null,
140
+ linkData: attributed
141
+ ? {
142
+ shortCode: install.short_code,
143
+ originalUrl: install.original_url,
144
+ iosUrl: install.ios_url,
145
+ androidUrl: install.android_url,
146
+ webFallbackUrl: install.web_fallback_url,
147
+ utmParameters: install.utm_parameters,
148
+ }
149
+ : null,
150
+ });
151
+ }
152
+ catch (error) {
153
+ fastify.log.error(`Error retrieving attribution: ${error}`);
154
+ return reply.status(500).send({
155
+ error: 'Failed to retrieve attribution data',
156
+ message: error.message,
157
+ });
158
+ }
159
+ });
160
+ /**
161
+ * POST /api/sdk/v1/event
162
+ * Track in-app events (purchases, signups, etc.)
163
+ * Used for conversion tracking and webhook triggers
164
+ *
165
+ * Request body:
166
+ * - installId: UUID of the install event
167
+ * - eventName: Name of the event (e.g., "purchase", "signup", "level_complete")
168
+ * - eventData: Optional JSON data associated with the event
169
+ * - timestamp: Optional event timestamp (defaults to now)
170
+ *
171
+ * Response:
172
+ * - eventId: UUID of the tracked event
173
+ * - acknowledged: Boolean confirmation
174
+ */
175
+ fastify.post('/api/sdk/v1/event', async (request, reply) => {
176
+ const schema = z.object({
177
+ installId: z.string().uuid(),
178
+ eventName: z.string(),
179
+ eventData: z.record(z.any()).optional(),
180
+ timestamp: z.string().datetime().optional(),
181
+ });
182
+ const body = schema.parse(request.body);
183
+ try {
184
+ // Verify install exists and get link_id for webhook lookup
185
+ const installCheck = await db.query(`SELECT id, link_id FROM install_events WHERE id = $1`, [body.installId]);
186
+ if (installCheck.rows.length === 0) {
187
+ return reply.status(404).send({
188
+ error: 'Install event not found',
189
+ });
190
+ }
191
+ const install = installCheck.rows[0];
192
+ const eventTimestamp = body.timestamp || new Date().toISOString();
193
+ // Insert event into in_app_events table
194
+ const eventResult = await db.query(`INSERT INTO in_app_events (install_id, event_name, event_data, event_timestamp)
195
+ VALUES ($1, $2, $3, $4)
196
+ RETURNING id`, [
197
+ body.installId,
198
+ body.eventName,
199
+ JSON.stringify(body.eventData || {}),
200
+ eventTimestamp,
201
+ ]);
202
+ const eventId = eventResult.rows[0].id;
203
+ fastify.log.info({
204
+ eventId,
205
+ installId: body.installId,
206
+ linkId: install.link_id,
207
+ eventName: body.eventName,
208
+ eventData: body.eventData,
209
+ timestamp: eventTimestamp,
210
+ });
211
+ // Trigger conversion_event webhooks if install was attributed to a link
212
+ if (install.link_id) {
213
+ // Query webhooks for the user who owns the link
214
+ const webhooksResult = await db.query(`SELECT w.*
215
+ FROM webhooks w
216
+ INNER JOIN links l ON l.user_id = w.user_id
217
+ WHERE l.id = $1 AND w.is_active = true`, [install.link_id]);
218
+ if (webhooksResult.rows.length > 0) {
219
+ const eventData = {
220
+ eventId,
221
+ installId: body.installId,
222
+ linkId: install.link_id,
223
+ eventName: body.eventName,
224
+ eventData: body.eventData || {},
225
+ timestamp: eventTimestamp,
226
+ };
227
+ // Trigger webhooks asynchronously (fire and forget)
228
+ setImmediate(async () => {
229
+ // Trigger webhooks without delivery logging (basic version)
230
+ // For delivery logging, use @linkforty/cloud premium features
231
+ triggerWebhooks(webhooksResult.rows, 'conversion_event', eventId, eventData).catch((error) => {
232
+ fastify.log.error('Failed to trigger conversion webhooks:', error);
233
+ });
234
+ });
235
+ }
236
+ }
237
+ return reply.status(200).send({
238
+ eventId,
239
+ acknowledged: true,
240
+ });
241
+ }
242
+ catch (error) {
243
+ fastify.log.error(`Error tracking event: ${error}`);
244
+ return reply.status(500).send({
245
+ error: 'Failed to track event',
246
+ message: error.message,
247
+ });
248
+ }
249
+ });
250
+ /**
251
+ * GET /api/sdk/v1/health
252
+ * Health check endpoint for SDK connectivity testing
253
+ */
254
+ fastify.get('/api/sdk/v1/health', async (request, reply) => {
255
+ return reply.status(200).send({
256
+ status: 'healthy',
257
+ version: 'v1',
258
+ timestamp: new Date().toISOString(),
259
+ });
260
+ });
261
+ }
262
+ //# sourceMappingURL=sdk.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sdk.js","sourceRoot":"","sources":["../../src/routes/sdk.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { FastifyInstance } from 'fastify';
2
+ export declare function webhookRoutes(fastify: FastifyInstance): Promise<void>;
3
+ //# sourceMappingURL=webhooks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhooks.d.ts","sourceRoot":"","sources":["../../src/routes/webhooks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAkB,MAAM,SAAS,CAAC;AA4B1D,wBAAsB,aAAa,CAAC,OAAO,EAAE,eAAe,iBA0N3D"}