@linkforty/core 1.0.0 → 1.2.0

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 (56) hide show
  1. package/dist/index.d.ts +4 -1
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +13 -1
  4. package/dist/index.js.map +1 -1
  5. package/dist/lib/database.d.ts.map +1 -1
  6. package/dist/lib/database.js +154 -0
  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 +29 -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 +387 -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 +1 -0
  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 +150 -0
  23. package/dist/lib/webhook.js.map +1 -0
  24. package/dist/routes/debug.d.ts +7 -0
  25. package/dist/routes/debug.d.ts.map +1 -0
  26. package/dist/routes/debug.js +321 -0
  27. package/dist/routes/debug.js.map +1 -0
  28. package/dist/routes/index.d.ts +5 -0
  29. package/dist/routes/index.d.ts.map +1 -1
  30. package/dist/routes/index.js +11 -1
  31. package/dist/routes/index.js.map +1 -1
  32. package/dist/routes/links.d.ts.map +1 -1
  33. package/dist/routes/links.js +21 -3
  34. package/dist/routes/links.js.map +1 -1
  35. package/dist/routes/preview.d.ts +3 -0
  36. package/dist/routes/preview.d.ts.map +1 -0
  37. package/dist/routes/preview.js +225 -0
  38. package/dist/routes/preview.js.map +1 -0
  39. package/dist/routes/qr.d.ts +6 -0
  40. package/dist/routes/qr.d.ts.map +1 -0
  41. package/dist/routes/qr.js +136 -0
  42. package/dist/routes/qr.js.map +1 -0
  43. package/dist/routes/redirect.d.ts.map +1 -1
  44. package/dist/routes/redirect.js +167 -11
  45. package/dist/routes/redirect.js.map +1 -1
  46. package/dist/routes/sdk.d.ts +7 -0
  47. package/dist/routes/sdk.d.ts.map +1 -0
  48. package/dist/routes/sdk.js +265 -0
  49. package/dist/routes/sdk.js.map +1 -0
  50. package/dist/routes/webhooks.d.ts +3 -0
  51. package/dist/routes/webhooks.d.ts.map +1 -0
  52. package/dist/routes/webhooks.js +212 -0
  53. package/dist/routes/webhooks.js.map +1 -0
  54. package/dist/types/index.d.ts +81 -0
  55. package/dist/types/index.d.ts.map +1 -1
  56. package/package.json +11 -7
@@ -0,0 +1,225 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.previewRoutes = previewRoutes;
4
+ const database_js_1 = require("../lib/database.js");
5
+ /**
6
+ * Detect if the request is from a social media scraper/bot
7
+ * These bots crawl links to generate previews when shared on social platforms
8
+ */
9
+ function isSocialScraper(userAgent) {
10
+ const scraperPatterns = [
11
+ /facebookexternalhit/i, // Facebook
12
+ /Facebot/i, // Facebook
13
+ /Twitterbot/i, // Twitter
14
+ /LinkedInBot/i, // LinkedIn
15
+ /Slackbot/i, // Slack
16
+ /Discordbot/i, // Discord
17
+ /TelegramBot/i, // Telegram
18
+ /WhatsApp/i, // WhatsApp
19
+ /PinterestBot/i, // Pinterest
20
+ /SkypeUriPreview/i, // Skype
21
+ /Googlebot/i, // Google (for search previews)
22
+ /bingbot/i, // Bing
23
+ /ia_archiver/i, // Alexa
24
+ ];
25
+ return scraperPatterns.some(pattern => pattern.test(userAgent));
26
+ }
27
+ /**
28
+ * Generate HTML preview page with Open Graph meta tags
29
+ */
30
+ function generatePreviewHTML(link, shortUrl, autoRedirect = true) {
31
+ // Use OG-specific values if provided, otherwise fall back to regular title/description
32
+ const ogTitle = link.og_title || link.title || 'Shared Link';
33
+ const ogDescription = link.og_description || link.description || '';
34
+ const ogImage = link.og_image_url || '';
35
+ const ogType = link.og_type || 'website';
36
+ const ogUrl = shortUrl;
37
+ // Auto-redirect after 2 seconds for human visitors
38
+ const metaRefresh = autoRedirect
39
+ ? `<meta http-equiv="refresh" content="2;url=${link.original_url}">`
40
+ : '';
41
+ return `<!DOCTYPE html>
42
+ <html lang="en">
43
+ <head>
44
+ <meta charset="UTF-8">
45
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
46
+ <title>${escapeHtml(ogTitle)}</title>
47
+
48
+ <!-- Open Graph / Facebook -->
49
+ <meta property="og:type" content="${escapeHtml(ogType)}">
50
+ <meta property="og:url" content="${escapeHtml(ogUrl)}">
51
+ <meta property="og:title" content="${escapeHtml(ogTitle)}">
52
+ <meta property="og:description" content="${escapeHtml(ogDescription)}">
53
+ ${ogImage ? `<meta property="og:image" content="${escapeHtml(ogImage)}">` : ''}
54
+ ${ogImage ? `<meta property="og:image:secure_url" content="${escapeHtml(ogImage)}">` : ''}
55
+
56
+ <!-- Twitter -->
57
+ <meta name="twitter:card" content="${ogImage ? 'summary_large_image' : 'summary'}">
58
+ <meta name="twitter:url" content="${escapeHtml(ogUrl)}">
59
+ <meta name="twitter:title" content="${escapeHtml(ogTitle)}">
60
+ <meta name="twitter:description" content="${escapeHtml(ogDescription)}">
61
+ ${ogImage ? `<meta name="twitter:image" content="${escapeHtml(ogImage)}">` : ''}
62
+
63
+ <!-- LinkedIn -->
64
+ <meta property="og:site_name" content="LinkForty">
65
+
66
+ ${metaRefresh}
67
+
68
+ <style>
69
+ body {
70
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
71
+ display: flex;
72
+ align-items: center;
73
+ justify-content: center;
74
+ min-height: 100vh;
75
+ margin: 0;
76
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
77
+ color: white;
78
+ text-align: center;
79
+ padding: 20px;
80
+ }
81
+ .container {
82
+ max-width: 600px;
83
+ }
84
+ h1 {
85
+ font-size: 2.5rem;
86
+ margin-bottom: 1rem;
87
+ }
88
+ p {
89
+ font-size: 1.25rem;
90
+ margin-bottom: 2rem;
91
+ opacity: 0.9;
92
+ }
93
+ .link {
94
+ display: inline-block;
95
+ padding: 12px 24px;
96
+ background: white;
97
+ color: #667eea;
98
+ text-decoration: none;
99
+ border-radius: 8px;
100
+ font-weight: 600;
101
+ transition: transform 0.2s;
102
+ }
103
+ .link:hover {
104
+ transform: translateY(-2px);
105
+ }
106
+ .loader {
107
+ margin: 2rem auto;
108
+ border: 4px solid rgba(255, 255, 255, 0.3);
109
+ border-top: 4px solid white;
110
+ border-radius: 50%;
111
+ width: 40px;
112
+ height: 40px;
113
+ animation: spin 1s linear infinite;
114
+ }
115
+ @keyframes spin {
116
+ 0% { transform: rotate(0deg); }
117
+ 100% { transform: rotate(360deg); }
118
+ }
119
+ </style>
120
+ </head>
121
+ <body>
122
+ <div class="container">
123
+ ${ogImage ? `<img src="${escapeHtml(ogImage)}" alt="${escapeHtml(ogTitle)}" style="max-width: 100%; border-radius: 12px; margin-bottom: 2rem; box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);">` : ''}
124
+ <h1>${escapeHtml(ogTitle)}</h1>
125
+ ${ogDescription ? `<p>${escapeHtml(ogDescription)}</p>` : ''}
126
+ ${autoRedirect ? '<div class="loader"></div><p>Redirecting you...</p>' : ''}
127
+ <a href="${escapeHtml(link.original_url)}" class="link">
128
+ ${autoRedirect ? 'Click here if not redirected' : 'Continue to destination'}
129
+ </a>
130
+ </div>
131
+ </body>
132
+ </html>`;
133
+ }
134
+ /**
135
+ * Escape HTML to prevent XSS
136
+ */
137
+ function escapeHtml(text) {
138
+ const map = {
139
+ '&': '&amp;',
140
+ '<': '&lt;',
141
+ '>': '&gt;',
142
+ '"': '&quot;',
143
+ "'": '&#039;',
144
+ };
145
+ return text.replace(/[&<>"']/g, (m) => map[m]);
146
+ }
147
+ async function previewRoutes(fastify) {
148
+ /**
149
+ * GET /:shortCode/preview
150
+ * Always return HTML preview page with Open Graph tags
151
+ * Auto-redirects after 2 seconds for human visitors
152
+ */
153
+ fastify.get('/:shortCode/preview', async (request, reply) => {
154
+ const { shortCode } = request.params;
155
+ const baseUrl = request.headers.host
156
+ ? `${request.protocol}://${request.headers.host}`
157
+ : 'https://link.forty';
158
+ try {
159
+ // Lookup link by short code
160
+ const result = await database_js_1.db.query(`SELECT * FROM links
161
+ WHERE short_code = $1 AND is_active = true
162
+ AND (expires_at IS NULL OR expires_at > NOW())`, [shortCode]);
163
+ if (result.rows.length === 0) {
164
+ return reply.status(404).send('Link not found');
165
+ }
166
+ const link = result.rows[0];
167
+ const shortUrl = `${baseUrl}/${shortCode}`;
168
+ // Return HTML with OG tags and auto-redirect
169
+ const html = generatePreviewHTML(link, shortUrl, true);
170
+ return reply
171
+ .header('Content-Type', 'text/html; charset=utf-8')
172
+ .send(html);
173
+ }
174
+ catch (error) {
175
+ fastify.log.error(`Error generating preview: ${error}`);
176
+ return reply.status(500).send('Error generating preview');
177
+ }
178
+ });
179
+ /**
180
+ * Middleware for /:shortCode route to detect social scrapers
181
+ * If scraper detected, return OG preview instead of redirecting
182
+ *
183
+ * Note: This should be registered BEFORE the main redirect route
184
+ */
185
+ fastify.addHook('preHandler', async (request, reply) => {
186
+ // Only apply to short code routes (not API routes, not preview routes)
187
+ const path = request.url.split('?')[0]; // Remove query string
188
+ if (path.startsWith('/api/') ||
189
+ path.endsWith('/preview') ||
190
+ path === '/' ||
191
+ path.includes('.')) {
192
+ return; // Skip this hook
193
+ }
194
+ const userAgent = request.headers['user-agent'] || '';
195
+ const shortCode = path.split('/').pop() || '';
196
+ const baseUrl = request.headers.host
197
+ ? `${request.protocol}://${request.headers.host}`
198
+ : 'https://link.forty';
199
+ // If it's a social scraper, return OG preview HTML
200
+ if (isSocialScraper(userAgent)) {
201
+ try {
202
+ const result = await database_js_1.db.query(`SELECT * FROM links
203
+ WHERE short_code = $1 AND is_active = true
204
+ AND (expires_at IS NULL OR expires_at > NOW())`, [shortCode]);
205
+ if (result.rows.length > 0) {
206
+ const link = result.rows[0];
207
+ const shortUrl = `${baseUrl}/${shortCode}`;
208
+ // Return HTML with OG tags, no auto-redirect for bots
209
+ const html = generatePreviewHTML(link, shortUrl, false);
210
+ reply
211
+ .header('Content-Type', 'text/html; charset=utf-8')
212
+ .send(html);
213
+ // Stop the request here, don't continue to redirect route
214
+ return reply;
215
+ }
216
+ }
217
+ catch (error) {
218
+ fastify.log.error(`Error in social scraper detection: ${error}`);
219
+ // Continue to normal redirect route on error
220
+ }
221
+ }
222
+ // Not a social scraper, continue to normal redirect logic
223
+ });
224
+ }
225
+ //# sourceMappingURL=preview.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preview.js","sourceRoot":"","sources":["../../src/routes/preview.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import { FastifyInstance } from 'fastify';
2
+ /**
3
+ * QR Code Routes - Generate QR codes for links
4
+ */
5
+ export declare function qrRoutes(fastify: FastifyInstance): Promise<void>;
6
+ //# sourceMappingURL=qr.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"qr.d.ts","sourceRoot":"","sources":["../../src/routes/qr.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAI1C;;GAEG;AACH,wBAAsB,QAAQ,CAAC,OAAO,EAAE,eAAe,iBAuItD"}
@@ -0,0 +1,136 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.qrRoutes = qrRoutes;
7
+ const qrcode_1 = __importDefault(require("qrcode"));
8
+ const database_js_1 = require("../lib/database.js");
9
+ /**
10
+ * QR Code Routes - Generate QR codes for links
11
+ */
12
+ async function qrRoutes(fastify) {
13
+ /**
14
+ * GET /api/links/:id/qr
15
+ * Generate QR code for a link
16
+ *
17
+ * Query parameters:
18
+ * - format: 'png' | 'svg' (default: 'png')
19
+ * - size: number 128-2048 (default: 512)
20
+ * - color: hex color for foreground (default: '#000000')
21
+ * - bgcolor: hex color for background (default: '#ffffff')
22
+ *
23
+ * Returns: QR code image (PNG or SVG)
24
+ */
25
+ fastify.get('/api/links/:id/qr', async (request, reply) => {
26
+ const { id } = request.params;
27
+ const query = request.query;
28
+ const format = (query.format || 'png');
29
+ const size = Math.min(Math.max(parseInt(query.size || '512', 10), 128), 2048);
30
+ const color = query.color || '#000000';
31
+ const bgcolor = query.bgcolor || '#ffffff';
32
+ // Validate format
33
+ if (!['png', 'svg'].includes(format)) {
34
+ return reply.status(400).send({ error: 'Invalid format. Use "png" or "svg".' });
35
+ }
36
+ // Build cache key
37
+ const cacheKey = `qr:${id}:${format}:${size}:${color}:${bgcolor}`;
38
+ // Try to get from cache
39
+ if (fastify.redis) {
40
+ try {
41
+ const cached = await fastify.redis.get(cacheKey);
42
+ if (cached) {
43
+ fastify.log.info(`QR code cache hit: ${cacheKey}`);
44
+ if (format === 'png') {
45
+ // Cached PNG is base64
46
+ const buffer = Buffer.from(cached, 'base64');
47
+ return reply
48
+ .type('image/png')
49
+ .header('Cache-Control', 'public, max-age=86400') // 24 hours
50
+ .send(buffer);
51
+ }
52
+ else {
53
+ // Cached SVG is text
54
+ return reply
55
+ .type('image/svg+xml')
56
+ .header('Cache-Control', 'public, max-age=86400')
57
+ .send(cached);
58
+ }
59
+ }
60
+ }
61
+ catch (error) {
62
+ fastify.log.warn('Redis QR cache lookup failed');
63
+ }
64
+ }
65
+ // Get link from database
66
+ const result = await database_js_1.db.query('SELECT short_code, original_url FROM links WHERE id = $1 AND is_active = true', [id]);
67
+ if (result.rows.length === 0) {
68
+ return reply.status(404).send({ error: 'Link not found' });
69
+ }
70
+ const link = result.rows[0];
71
+ // Build short URL (use original_url as fallback if short_code not available)
72
+ // In production, you'd want to use your actual domain
73
+ const shortUrl = link.short_code
74
+ ? `${request.protocol}://${request.hostname}/${link.short_code}`
75
+ : link.original_url;
76
+ try {
77
+ // QR code options
78
+ const options = {
79
+ errorCorrectionLevel: 'M', // Medium error correction
80
+ margin: 1, // Quiet zone margin
81
+ width: size,
82
+ color: {
83
+ dark: color,
84
+ light: bgcolor,
85
+ },
86
+ };
87
+ if (format === 'png') {
88
+ // Generate PNG as buffer
89
+ const buffer = await qrcode_1.default.toBuffer(shortUrl, options);
90
+ // Cache as base64
91
+ if (fastify.redis) {
92
+ try {
93
+ await fastify.redis.setex(cacheKey, 86400, buffer.toString('base64')); // 24 hour TTL
94
+ }
95
+ catch (error) {
96
+ fastify.log.warn('Failed to cache QR code');
97
+ }
98
+ }
99
+ return reply
100
+ .type('image/png')
101
+ .header('Cache-Control', 'public, max-age=86400')
102
+ .header('Content-Disposition', `inline; filename="qr-${link.short_code || 'code'}.png"`)
103
+ .send(buffer);
104
+ }
105
+ else {
106
+ // Generate SVG as string
107
+ const svg = await qrcode_1.default.toString(shortUrl, {
108
+ ...options,
109
+ type: 'svg',
110
+ });
111
+ // Cache SVG text
112
+ if (fastify.redis) {
113
+ try {
114
+ await fastify.redis.setex(cacheKey, 86400, svg);
115
+ }
116
+ catch (error) {
117
+ fastify.log.warn('Failed to cache QR code');
118
+ }
119
+ }
120
+ return reply
121
+ .type('image/svg+xml')
122
+ .header('Cache-Control', 'public, max-age=86400')
123
+ .header('Content-Disposition', `inline; filename="qr-${link.short_code || 'code'}.svg"`)
124
+ .send(svg);
125
+ }
126
+ }
127
+ catch (error) {
128
+ fastify.log.error(`QR code generation failed: ${error.message}`);
129
+ return reply.status(500).send({
130
+ error: 'Failed to generate QR code',
131
+ message: error.message
132
+ });
133
+ }
134
+ });
135
+ }
136
+ //# sourceMappingURL=qr.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"qr.js","sourceRoot":"","sources":["../../src/routes/qr.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"redirect.d.ts","sourceRoot":"","sources":["../../src/routes/redirect.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAI1C,wBAAsB,cAAc,CAAC,OAAO,EAAE,eAAe,iBA4J5D"}
1
+ {"version":3,"file":"redirect.d.ts","sourceRoot":"","sources":["../../src/routes/redirect.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAM1C,wBAAsB,cAAc,CAAC,OAAO,EAAE,eAAe,iBAsS5D"}
@@ -1,15 +1,50 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  Object.defineProperty(exports, "__esModule", { value: true });
3
36
  exports.redirectRoutes = redirectRoutes;
4
37
  const database_js_1 = require("../lib/database.js");
5
38
  const utils_js_1 = require("../lib/utils.js");
39
+ const fingerprint_js_1 = require("../lib/fingerprint.js");
40
+ const event_emitter_js_1 = require("../lib/event-emitter.js");
6
41
  async function redirectRoutes(fastify) {
7
- // Handle short link redirects
8
- fastify.get('/:shortCode', async (request, reply) => {
9
- const { shortCode } = request.params;
42
+ // Helper function to handle the actual redirect logic
43
+ async function handleRedirect(request, reply, shortCode, templateSlug) {
10
44
  let linkData = null;
45
+ // Build cache key (include template if present)
46
+ const cacheKey = templateSlug ? `link:${templateSlug}:${shortCode}` : `link:${shortCode}`;
11
47
  // Try to get link from cache if Redis is available
12
- const cacheKey = `link:${shortCode}`;
13
48
  if (fastify.redis) {
14
49
  try {
15
50
  linkData = await fastify.redis.get(cacheKey);
@@ -19,10 +54,30 @@ async function redirectRoutes(fastify) {
19
54
  }
20
55
  }
21
56
  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]);
57
+ // Build query based on whether template slug is provided
58
+ let query;
59
+ let params;
60
+ if (templateSlug) {
61
+ // Template-based URL: verify both template and link match
62
+ query = `
63
+ SELECT l.* FROM links l
64
+ LEFT JOIN link_templates t ON l.template_id = t.id
65
+ WHERE l.short_code = $1 AND t.slug = $2
66
+ AND l.is_active = true
67
+ AND (l.expires_at IS NULL OR l.expires_at > NOW())
68
+ `;
69
+ params = [shortCode, templateSlug];
70
+ }
71
+ else {
72
+ // Legacy URL: just lookup by short code
73
+ query = `
74
+ SELECT * FROM links
75
+ WHERE short_code = $1 AND is_active = true
76
+ AND (expires_at IS NULL OR expires_at > NOW())
77
+ `;
78
+ params = [shortCode];
79
+ }
80
+ const result = await database_js_1.db.query(query, params);
26
81
  if (result.rows.length === 0) {
27
82
  return reply.status(404).send({ error: 'Link not found' });
28
83
  }
@@ -81,18 +136,27 @@ async function redirectRoutes(fastify) {
81
136
  const userAgent = request.headers['user-agent'] || '';
82
137
  const ip = request.ip;
83
138
  const referrer = request.headers.referer || null;
84
- const { deviceType, platform } = (0, utils_js_1.parseUserAgent)(userAgent);
139
+ const acceptLanguage = request.headers['accept-language'] || '';
140
+ const deviceType = (0, utils_js_1.detectDevice)(userAgent);
141
+ const { platform, platformVersion } = (0, utils_js_1.parseUserAgent)(userAgent);
85
142
  const { countryCode, countryName, region, city, latitude, longitude, timezone } = (0, utils_js_1.getLocationFromIP)(ip);
86
143
  // Extract UTM parameters from query string
87
144
  const query = request.query;
88
145
  const utmSource = query?.utm_source;
89
146
  const utmMedium = query?.utm_medium;
90
147
  const utmCampaign = query?.utm_campaign;
91
- await database_js_1.db.query(`INSERT INTO click_events (
148
+ // Extract fingerprint data from query params (sent by SDK/client)
149
+ const fpTimezone = query?.fp_tz || timezone || undefined;
150
+ const fpLanguage = query?.fp_lang || acceptLanguage.split(',')[0]?.split(';')[0] || undefined;
151
+ const fpScreenWidth = query?.fp_sw ? parseInt(query.fp_sw, 10) : undefined;
152
+ const fpScreenHeight = query?.fp_sh ? parseInt(query.fp_sh, 10) : undefined;
153
+ // Insert click event
154
+ const clickResult = await database_js_1.db.query(`INSERT INTO click_events (
92
155
  link_id, ip_address, user_agent, device_type, platform,
93
156
  country_code, country_name, region, city, latitude, longitude, timezone,
94
157
  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)`, [
158
+ ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16)
159
+ RETURNING id`, [
96
160
  link.id,
97
161
  ip,
98
162
  userAgent,
@@ -110,6 +174,88 @@ async function redirectRoutes(fastify) {
110
174
  utmCampaign,
111
175
  referrer,
112
176
  ]);
177
+ const clickId = clickResult.rows[0].id;
178
+ // Store device fingerprint for deferred deep linking
179
+ const fingerprintData = {
180
+ ipAddress: ip,
181
+ userAgent,
182
+ timezone: fpTimezone,
183
+ language: fpLanguage,
184
+ screenWidth: fpScreenWidth,
185
+ screenHeight: fpScreenHeight,
186
+ platform: deviceType,
187
+ platformVersion,
188
+ };
189
+ await (0, fingerprint_js_1.storeFingerprintForClick)(clickId, fingerprintData);
190
+ // Determine redirect URL for event emission
191
+ let redirectUrl = link.original_url;
192
+ let redirectReason = 'original_url';
193
+ if (deviceType === 'ios' && link.ios_url) {
194
+ redirectUrl = link.ios_url;
195
+ redirectReason = 'ios_url';
196
+ }
197
+ else if (deviceType === 'android' && link.android_url) {
198
+ redirectUrl = link.android_url;
199
+ redirectReason = 'android_url';
200
+ }
201
+ else if (deviceType === 'web' && link.web_fallback_url) {
202
+ redirectUrl = link.web_fallback_url;
203
+ redirectReason = 'web_fallback_url';
204
+ }
205
+ const finalRedirectUrl = (0, utils_js_1.buildRedirectUrl)(redirectUrl, link.utm_parameters);
206
+ // Emit click event for real-time streaming to WebSocket clients
207
+ (0, event_emitter_js_1.emitClickEvent)({
208
+ eventId: clickId,
209
+ timestamp: new Date().toISOString(),
210
+ linkId: link.id,
211
+ shortCode: link.short_code,
212
+ userId: link.user_id,
213
+ ipAddress: ip,
214
+ userAgent,
215
+ country: countryCode || undefined,
216
+ city: city || undefined,
217
+ deviceType,
218
+ platform: platform || undefined,
219
+ redirectUrl: finalRedirectUrl,
220
+ redirectReason,
221
+ targetingMatched: true, // If we got here, targeting matched
222
+ utmParameters: link.utm_parameters || undefined,
223
+ referer: referrer || undefined,
224
+ language: fpLanguage,
225
+ });
226
+ // Trigger webhooks for click_event
227
+ try {
228
+ const webhooksResult = await database_js_1.db.query('SELECT * FROM webhooks WHERE user_id = $1 AND is_active = true', [link.user_id]);
229
+ if (webhooksResult.rows.length > 0) {
230
+ const { triggerWebhooks } = await Promise.resolve().then(() => __importStar(require('../lib/webhook.js')));
231
+ const clickEventData = {
232
+ id: clickId,
233
+ linkId: link.id,
234
+ clickedAt: new Date().toISOString(),
235
+ ipAddress: ip,
236
+ userAgent,
237
+ deviceType,
238
+ platform,
239
+ countryCode,
240
+ countryName,
241
+ region,
242
+ city,
243
+ latitude,
244
+ longitude,
245
+ timezone,
246
+ utmSource,
247
+ utmMedium,
248
+ utmCampaign,
249
+ referrer,
250
+ };
251
+ // Trigger webhooks without delivery logging (basic version)
252
+ // For delivery logging, use @linkforty/cloud premium features
253
+ await triggerWebhooks(webhooksResult.rows, 'click_event', clickId, clickEventData);
254
+ }
255
+ }
256
+ catch (webhookError) {
257
+ fastify.log.error(`Error triggering click webhooks: ${webhookError}`);
258
+ }
113
259
  }
114
260
  catch (error) {
115
261
  fastify.log.error(`Error tracking click: ${error}`);
@@ -133,6 +279,16 @@ async function redirectRoutes(fastify) {
133
279
  const finalUrl = (0, utils_js_1.buildRedirectUrl)(redirectUrl, link.utm_parameters);
134
280
  // Redirect
135
281
  return reply.redirect(302, finalUrl);
282
+ }
283
+ // Template-based shortlink route: /:templateSlug/:shortCode
284
+ fastify.get('/:templateSlug/:shortCode', async (request, reply) => {
285
+ const { templateSlug, shortCode } = request.params;
286
+ return handleRedirect(request, reply, shortCode, templateSlug);
287
+ });
288
+ // Legacy shortlink route (no template): /:shortCode
289
+ fastify.get('/:shortCode', async (request, reply) => {
290
+ const { shortCode } = request.params;
291
+ return handleRedirect(request, reply, shortCode);
136
292
  });
137
293
  }
138
294
  //# 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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"}