@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
package/dist/routes/redirect.js
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
//
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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 =
|
|
48
|
-
const { countryCode } =
|
|
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
|
|
85
|
-
const
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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":"
|
|
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 @@
|
|
|
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"}
|