@linkforty/core 1.6.6 → 1.13.7
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/README.md +8 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/client-ip.d.ts +10 -0
- package/dist/lib/client-ip.d.ts.map +1 -0
- package/dist/lib/client-ip.js +18 -0
- package/dist/lib/client-ip.js.map +1 -0
- package/dist/lib/client-ip.test.d.ts +7 -0
- package/dist/lib/client-ip.test.d.ts.map +1 -0
- package/dist/lib/client-ip.test.js +76 -0
- package/dist/lib/client-ip.test.js.map +1 -0
- package/dist/lib/event-emitter.test.d.ts +2 -0
- package/dist/lib/event-emitter.test.d.ts.map +1 -0
- package/dist/lib/event-emitter.test.js +162 -0
- package/dist/lib/event-emitter.test.js.map +1 -0
- package/dist/lib/fingerprint.test.d.ts +2 -0
- package/dist/lib/fingerprint.test.d.ts.map +1 -0
- package/dist/lib/fingerprint.test.js +227 -0
- package/dist/lib/fingerprint.test.js.map +1 -0
- package/dist/lib/utils.d.ts +1 -1
- package/dist/lib/utils.d.ts.map +1 -1
- package/dist/lib/utils.js +16 -8
- package/dist/lib/utils.js.map +1 -1
- package/dist/lib/webhook.js +1 -1
- package/dist/lib/webhook.test.d.ts +2 -0
- package/dist/lib/webhook.test.d.ts.map +1 -0
- package/dist/lib/webhook.test.js +289 -0
- package/dist/lib/webhook.test.js.map +1 -0
- package/dist/routes/redirect.d.ts.map +1 -1
- package/dist/routes/redirect.js +68 -24
- package/dist/routes/redirect.js.map +1 -1
- package/dist/routes/sdk.d.ts.map +1 -1
- package/dist/routes/sdk.js +16 -7
- package/dist/routes/sdk.js.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +4 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fingerprint.test.js","sourceRoot":"","sources":["../../src/lib/fingerprint.test.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/utils.d.ts
CHANGED
|
@@ -22,6 +22,6 @@ export declare function getLocationFromIP(ip: string): {
|
|
|
22
22
|
longitude: number | null;
|
|
23
23
|
timezone: string;
|
|
24
24
|
};
|
|
25
|
-
export declare function buildRedirectUrl(originalUrl: string, utmParameters?: Record<string, string>): string;
|
|
25
|
+
export declare function buildRedirectUrl(originalUrl: string | null | undefined, utmParameters?: Record<string, string>): string | null;
|
|
26
26
|
export declare function detectDevice(userAgent: string): 'ios' | 'android' | 'web';
|
|
27
27
|
//# sourceMappingURL=utils.d.ts.map
|
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;;;;;EAU/C;AAoDD,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,MAAM;;;;;;;;;;;;;;;;EAwB3C;AAED,wBAAgB,gBAAgB,CAC9B,WAAW,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,GAAG,IAAI,GAAG,SAAS,EACtC,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GACrC,MAAM,GAAG,IAAI,CAmBf;AAED,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,KAAK,GAAG,SAAS,GAAG,KAAK,CAYzE"}
|
package/dist/lib/utils.js
CHANGED
|
@@ -87,15 +87,23 @@ export function getLocationFromIP(ip) {
|
|
|
87
87
|
};
|
|
88
88
|
}
|
|
89
89
|
export function buildRedirectUrl(originalUrl, utmParameters) {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
90
|
+
if (!originalUrl)
|
|
91
|
+
return null;
|
|
92
|
+
try {
|
|
93
|
+
const url = new URL(originalUrl);
|
|
94
|
+
if (utmParameters) {
|
|
95
|
+
Object.entries(utmParameters).forEach(([key, value]) => {
|
|
96
|
+
if (value) {
|
|
97
|
+
url.searchParams.set(`utm_${key}`, value);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
return url.toString();
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
// If URL is invalid, return it as-is rather than crashing
|
|
105
|
+
return originalUrl;
|
|
97
106
|
}
|
|
98
|
-
return url.toString();
|
|
99
107
|
}
|
|
100
108
|
export function detectDevice(userAgent) {
|
|
101
109
|
const ua = userAgent.toLowerCase();
|
package/dist/lib/utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/lib/utils.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/lib/utils.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/webhook.js
CHANGED
|
@@ -75,7 +75,7 @@ async function attemptWebhookDelivery(webhook, payload, attemptNumber) {
|
|
|
75
75
|
* Deliver webhook with retry logic and exponential backoff
|
|
76
76
|
*/
|
|
77
77
|
export async function deliverWebhook(webhook, payload, logDelivery) {
|
|
78
|
-
const maxRetries = webhook.retry_count
|
|
78
|
+
const maxRetries = webhook.retry_count ?? 3;
|
|
79
79
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
80
80
|
const result = await attemptWebhookDelivery(webhook, payload, attempt);
|
|
81
81
|
// Log delivery attempt if logging function provided
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook.test.d.ts","sourceRoot":"","sources":["../../src/lib/webhook.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { generateWebhookSignature, generateWebhookSecret, deliverWebhook, triggerWebhooks, } from './webhook.js';
|
|
3
|
+
// Helper to build a Webhook fixture
|
|
4
|
+
function makeWebhook(overrides = {}) {
|
|
5
|
+
return {
|
|
6
|
+
id: 'wh-1',
|
|
7
|
+
user_id: 'user-1',
|
|
8
|
+
name: 'Test Webhook',
|
|
9
|
+
url: 'https://example.com/hook',
|
|
10
|
+
secret: 'test-secret',
|
|
11
|
+
events: ['click_event'],
|
|
12
|
+
is_active: true,
|
|
13
|
+
retry_count: 1,
|
|
14
|
+
timeout_ms: 5000,
|
|
15
|
+
headers: {},
|
|
16
|
+
created_at: '2024-01-01T00:00:00.000Z',
|
|
17
|
+
updated_at: '2024-01-01T00:00:00.000Z',
|
|
18
|
+
...overrides,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
// Helper to build a WebhookPayload fixture
|
|
22
|
+
function makePayload(overrides = {}) {
|
|
23
|
+
return {
|
|
24
|
+
event: 'click_event',
|
|
25
|
+
event_id: 'evt-1',
|
|
26
|
+
timestamp: '2024-01-01T00:00:00.000Z',
|
|
27
|
+
data: { id: 'data-1' },
|
|
28
|
+
...overrides,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
// Helper to make a minimal Response-like mock
|
|
32
|
+
function mockResponse(ok, status, statusText, body = '') {
|
|
33
|
+
return {
|
|
34
|
+
ok,
|
|
35
|
+
status,
|
|
36
|
+
statusText,
|
|
37
|
+
text: () => Promise.resolve(body),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
describe('generateWebhookSignature', () => {
|
|
41
|
+
it('returns a hex string', () => {
|
|
42
|
+
const sig = generateWebhookSignature('payload', 'secret');
|
|
43
|
+
expect(typeof sig).toBe('string');
|
|
44
|
+
expect(sig).toMatch(/^[0-9a-f]{64}$/);
|
|
45
|
+
});
|
|
46
|
+
it('is deterministic for the same inputs', () => {
|
|
47
|
+
const sig1 = generateWebhookSignature('hello', 'key');
|
|
48
|
+
const sig2 = generateWebhookSignature('hello', 'key');
|
|
49
|
+
expect(sig1).toBe(sig2);
|
|
50
|
+
});
|
|
51
|
+
it('produces different signatures for different payloads', () => {
|
|
52
|
+
const sig1 = generateWebhookSignature('payload-a', 'secret');
|
|
53
|
+
const sig2 = generateWebhookSignature('payload-b', 'secret');
|
|
54
|
+
expect(sig1).not.toBe(sig2);
|
|
55
|
+
});
|
|
56
|
+
it('produces different signatures for different secrets', () => {
|
|
57
|
+
const sig1 = generateWebhookSignature('payload', 'secret-a');
|
|
58
|
+
const sig2 = generateWebhookSignature('payload', 'secret-b');
|
|
59
|
+
expect(sig1).not.toBe(sig2);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
describe('generateWebhookSecret', () => {
|
|
63
|
+
it('returns a 64-character hex string (32 random bytes)', () => {
|
|
64
|
+
const secret = generateWebhookSecret();
|
|
65
|
+
expect(typeof secret).toBe('string');
|
|
66
|
+
expect(secret).toMatch(/^[0-9a-f]{64}$/);
|
|
67
|
+
});
|
|
68
|
+
it('returns unique values on each call', () => {
|
|
69
|
+
const s1 = generateWebhookSecret();
|
|
70
|
+
const s2 = generateWebhookSecret();
|
|
71
|
+
expect(s1).not.toBe(s2);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
describe('deliverWebhook', () => {
|
|
75
|
+
beforeEach(() => {
|
|
76
|
+
vi.useFakeTimers();
|
|
77
|
+
});
|
|
78
|
+
afterEach(() => {
|
|
79
|
+
vi.restoreAllMocks();
|
|
80
|
+
vi.unstubAllGlobals();
|
|
81
|
+
vi.useRealTimers();
|
|
82
|
+
});
|
|
83
|
+
it('returns a success result on HTTP 200', async () => {
|
|
84
|
+
vi.stubGlobal('fetch', vi.fn().mockResolvedValue(mockResponse(true, 200, 'OK', 'received')));
|
|
85
|
+
const webhook = makeWebhook();
|
|
86
|
+
const payload = makePayload();
|
|
87
|
+
const result = await deliverWebhook(webhook, payload);
|
|
88
|
+
expect(result.success).toBe(true);
|
|
89
|
+
expect(result.webhookId).toBe(webhook.id);
|
|
90
|
+
expect(result.eventType).toBe(payload.event);
|
|
91
|
+
expect(result.eventId).toBe(payload.event_id);
|
|
92
|
+
expect(result.responseStatus).toBe(200);
|
|
93
|
+
expect(result.responseBody).toBe('received');
|
|
94
|
+
expect(result.attemptNumber).toBe(1);
|
|
95
|
+
expect(result.deliveredAt).toBeDefined();
|
|
96
|
+
expect(result.errorMessage).toBeUndefined();
|
|
97
|
+
});
|
|
98
|
+
it('returns a failure result on HTTP 500', async () => {
|
|
99
|
+
vi.stubGlobal('fetch', vi.fn().mockResolvedValue(mockResponse(false, 500, 'Internal Server Error', 'error body')));
|
|
100
|
+
// Use logDelivery to capture per-attempt details (the final return is a
|
|
101
|
+
// generic "Failed after N attempts" object once retries are exhausted).
|
|
102
|
+
const logDelivery = vi.fn().mockResolvedValue(undefined);
|
|
103
|
+
const webhook = makeWebhook({ retry_count: 1 });
|
|
104
|
+
const payload = makePayload();
|
|
105
|
+
const result = await deliverWebhook(webhook, payload, logDelivery);
|
|
106
|
+
// Per-attempt result captured via logDelivery
|
|
107
|
+
const attemptResult = logDelivery.mock.calls[0][0];
|
|
108
|
+
expect(attemptResult.responseStatus).toBe(500);
|
|
109
|
+
expect(attemptResult.errorMessage).toBe('HTTP 500: Internal Server Error');
|
|
110
|
+
expect(attemptResult.responseBody).toBe('error body');
|
|
111
|
+
// Final returned result after retries exhausted
|
|
112
|
+
expect(result.success).toBe(false);
|
|
113
|
+
expect(result.deliveredAt).toBeUndefined();
|
|
114
|
+
});
|
|
115
|
+
it('sets correct request headers', async () => {
|
|
116
|
+
const mockFetch = vi.fn().mockResolvedValue(mockResponse(true, 200, 'OK'));
|
|
117
|
+
vi.stubGlobal('fetch', mockFetch);
|
|
118
|
+
const webhook = makeWebhook({ headers: { 'X-Custom': 'value' } });
|
|
119
|
+
const payload = makePayload();
|
|
120
|
+
await deliverWebhook(webhook, payload);
|
|
121
|
+
const [url, init] = mockFetch.mock.calls[0];
|
|
122
|
+
expect(url).toBe(webhook.url);
|
|
123
|
+
expect(init.method).toBe('POST');
|
|
124
|
+
const headers = init.headers;
|
|
125
|
+
expect(headers['Content-Type']).toBe('application/json');
|
|
126
|
+
expect(headers['X-LinkForty-Event']).toBe(payload.event);
|
|
127
|
+
expect(headers['X-LinkForty-Event-ID']).toBe(payload.event_id);
|
|
128
|
+
expect(headers['X-LinkForty-Signature']).toMatch(/^sha256=[0-9a-f]{64}$/);
|
|
129
|
+
expect(headers['User-Agent']).toBe('LinkForty-Webhook/1.0');
|
|
130
|
+
expect(headers['X-Custom']).toBe('value');
|
|
131
|
+
});
|
|
132
|
+
it('sends the correct JSON body', async () => {
|
|
133
|
+
const mockFetch = vi.fn().mockResolvedValue(mockResponse(true, 200, 'OK'));
|
|
134
|
+
vi.stubGlobal('fetch', mockFetch);
|
|
135
|
+
const webhook = makeWebhook();
|
|
136
|
+
const payload = makePayload();
|
|
137
|
+
await deliverWebhook(webhook, payload);
|
|
138
|
+
const [, init] = mockFetch.mock.calls[0];
|
|
139
|
+
expect(JSON.parse(init.body)).toEqual(payload);
|
|
140
|
+
});
|
|
141
|
+
it('truncates response body to 1000 characters', async () => {
|
|
142
|
+
const longBody = 'x'.repeat(2000);
|
|
143
|
+
vi.stubGlobal('fetch', vi.fn().mockResolvedValue(mockResponse(true, 200, 'OK', longBody)));
|
|
144
|
+
const result = await deliverWebhook(makeWebhook(), makePayload());
|
|
145
|
+
expect(result.responseBody).toHaveLength(1000);
|
|
146
|
+
});
|
|
147
|
+
it('retries on failure and succeeds on second attempt', async () => {
|
|
148
|
+
const mockFetch = vi.fn()
|
|
149
|
+
.mockResolvedValueOnce(mockResponse(false, 503, 'Service Unavailable'))
|
|
150
|
+
.mockResolvedValueOnce(mockResponse(true, 200, 'OK'));
|
|
151
|
+
vi.stubGlobal('fetch', mockFetch);
|
|
152
|
+
const webhook = makeWebhook({ retry_count: 2 });
|
|
153
|
+
const payload = makePayload();
|
|
154
|
+
const promise = deliverWebhook(webhook, payload);
|
|
155
|
+
await vi.runAllTimersAsync();
|
|
156
|
+
const result = await promise;
|
|
157
|
+
expect(mockFetch).toHaveBeenCalledTimes(2);
|
|
158
|
+
expect(result.success).toBe(true);
|
|
159
|
+
expect(result.attemptNumber).toBe(2);
|
|
160
|
+
});
|
|
161
|
+
it('exhausts all retries and returns final failure', async () => {
|
|
162
|
+
vi.stubGlobal('fetch', vi.fn().mockResolvedValue(mockResponse(false, 500, 'Error')));
|
|
163
|
+
const webhook = makeWebhook({ retry_count: 3 });
|
|
164
|
+
const payload = makePayload();
|
|
165
|
+
const promise = deliverWebhook(webhook, payload);
|
|
166
|
+
await vi.runAllTimersAsync();
|
|
167
|
+
const result = await promise;
|
|
168
|
+
expect(result.success).toBe(false);
|
|
169
|
+
expect(result.errorMessage).toBe('Failed after 3 attempts');
|
|
170
|
+
expect(result.attemptNumber).toBe(3);
|
|
171
|
+
});
|
|
172
|
+
it('calls logDelivery for each attempt', async () => {
|
|
173
|
+
const mockFetch = vi.fn()
|
|
174
|
+
.mockResolvedValueOnce(mockResponse(false, 500, 'Error'))
|
|
175
|
+
.mockResolvedValueOnce(mockResponse(true, 200, 'OK'));
|
|
176
|
+
vi.stubGlobal('fetch', mockFetch);
|
|
177
|
+
const logDelivery = vi.fn().mockResolvedValue(undefined);
|
|
178
|
+
const webhook = makeWebhook({ retry_count: 2 });
|
|
179
|
+
const payload = makePayload();
|
|
180
|
+
const promise = deliverWebhook(webhook, payload, logDelivery);
|
|
181
|
+
await vi.runAllTimersAsync();
|
|
182
|
+
await promise;
|
|
183
|
+
expect(logDelivery).toHaveBeenCalledTimes(2);
|
|
184
|
+
expect(logDelivery.mock.calls[0][0].success).toBe(false);
|
|
185
|
+
expect(logDelivery.mock.calls[1][0].success).toBe(true);
|
|
186
|
+
});
|
|
187
|
+
it('continues even if logDelivery throws', async () => {
|
|
188
|
+
vi.stubGlobal('fetch', vi.fn().mockResolvedValue(mockResponse(true, 200, 'OK')));
|
|
189
|
+
const logDelivery = vi.fn().mockRejectedValue(new Error('log failed'));
|
|
190
|
+
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => { });
|
|
191
|
+
const result = await deliverWebhook(makeWebhook(), makePayload(), logDelivery);
|
|
192
|
+
expect(result.success).toBe(true);
|
|
193
|
+
consoleSpy.mockRestore();
|
|
194
|
+
});
|
|
195
|
+
it('captures timeout error in logDelivery when fetch exceeds timeout_ms', async () => {
|
|
196
|
+
const abortError = new Error('The operation was aborted');
|
|
197
|
+
abortError.name = 'AbortError';
|
|
198
|
+
vi.stubGlobal('fetch', vi.fn().mockRejectedValue(abortError));
|
|
199
|
+
const logDelivery = vi.fn().mockResolvedValue(undefined);
|
|
200
|
+
const webhook = makeWebhook({ timeout_ms: 1000, retry_count: 1 });
|
|
201
|
+
const result = await deliverWebhook(webhook, makePayload(), logDelivery);
|
|
202
|
+
// Per-attempt error is exposed via logDelivery
|
|
203
|
+
const attemptResult = logDelivery.mock.calls[0][0];
|
|
204
|
+
expect(attemptResult.success).toBe(false);
|
|
205
|
+
expect(attemptResult.errorMessage).toBe('Timeout after 1000ms');
|
|
206
|
+
// Final result reflects exhausted retries
|
|
207
|
+
expect(result.success).toBe(false);
|
|
208
|
+
});
|
|
209
|
+
it('captures network error message in logDelivery', async () => {
|
|
210
|
+
vi.stubGlobal('fetch', vi.fn().mockRejectedValue(new Error('ECONNREFUSED')));
|
|
211
|
+
const logDelivery = vi.fn().mockResolvedValue(undefined);
|
|
212
|
+
const webhook = makeWebhook({ retry_count: 1 });
|
|
213
|
+
const result = await deliverWebhook(webhook, makePayload(), logDelivery);
|
|
214
|
+
const attemptResult = logDelivery.mock.calls[0][0];
|
|
215
|
+
expect(attemptResult.success).toBe(false);
|
|
216
|
+
expect(attemptResult.errorMessage).toBe('ECONNREFUSED');
|
|
217
|
+
expect(result.success).toBe(false);
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
describe('triggerWebhooks', () => {
|
|
221
|
+
beforeEach(() => {
|
|
222
|
+
vi.useFakeTimers();
|
|
223
|
+
});
|
|
224
|
+
afterEach(() => {
|
|
225
|
+
vi.restoreAllMocks();
|
|
226
|
+
vi.unstubAllGlobals();
|
|
227
|
+
vi.useRealTimers();
|
|
228
|
+
});
|
|
229
|
+
it('does nothing when no webhooks provided', async () => {
|
|
230
|
+
const mockFetch = vi.fn();
|
|
231
|
+
vi.stubGlobal('fetch', mockFetch);
|
|
232
|
+
await triggerWebhooks([], 'click_event', 'evt-1', {});
|
|
233
|
+
expect(mockFetch).not.toHaveBeenCalled();
|
|
234
|
+
});
|
|
235
|
+
it('skips inactive webhooks', async () => {
|
|
236
|
+
const mockFetch = vi.fn();
|
|
237
|
+
vi.stubGlobal('fetch', mockFetch);
|
|
238
|
+
const inactive = makeWebhook({ is_active: false });
|
|
239
|
+
await triggerWebhooks([inactive], 'click_event', 'evt-1', {});
|
|
240
|
+
expect(mockFetch).not.toHaveBeenCalled();
|
|
241
|
+
});
|
|
242
|
+
it('skips webhooks not subscribed to the event', async () => {
|
|
243
|
+
const mockFetch = vi.fn();
|
|
244
|
+
vi.stubGlobal('fetch', mockFetch);
|
|
245
|
+
const webhook = makeWebhook({ events: ['install_event'] });
|
|
246
|
+
await triggerWebhooks([webhook], 'click_event', 'evt-1', {});
|
|
247
|
+
expect(mockFetch).not.toHaveBeenCalled();
|
|
248
|
+
});
|
|
249
|
+
it('delivers to matching active webhooks', async () => {
|
|
250
|
+
const mockFetch = vi.fn().mockResolvedValue(mockResponse(true, 200, 'OK'));
|
|
251
|
+
vi.stubGlobal('fetch', mockFetch);
|
|
252
|
+
const webhook = makeWebhook({ events: ['click_event', 'install_event'] });
|
|
253
|
+
triggerWebhooks([webhook], 'click_event', 'evt-1', { foo: 'bar' });
|
|
254
|
+
await vi.runAllTimersAsync();
|
|
255
|
+
expect(mockFetch).toHaveBeenCalledOnce();
|
|
256
|
+
const body = JSON.parse(mockFetch.mock.calls[0][1].body);
|
|
257
|
+
expect(body.event).toBe('click_event');
|
|
258
|
+
expect(body.event_id).toBe('evt-1');
|
|
259
|
+
expect(body.data).toEqual({ foo: 'bar' });
|
|
260
|
+
});
|
|
261
|
+
it('delivers to multiple matching webhooks', async () => {
|
|
262
|
+
const mockFetch = vi.fn().mockResolvedValue(mockResponse(true, 200, 'OK'));
|
|
263
|
+
vi.stubGlobal('fetch', mockFetch);
|
|
264
|
+
const wh1 = makeWebhook({ id: 'wh-1', url: 'https://a.com/hook' });
|
|
265
|
+
const wh2 = makeWebhook({ id: 'wh-2', url: 'https://b.com/hook' });
|
|
266
|
+
triggerWebhooks([wh1, wh2], 'click_event', 'evt-2', {});
|
|
267
|
+
await vi.runAllTimersAsync();
|
|
268
|
+
expect(mockFetch).toHaveBeenCalledTimes(2);
|
|
269
|
+
});
|
|
270
|
+
it('calls logDelivery with webhookId and result', async () => {
|
|
271
|
+
const mockFetch = vi.fn().mockResolvedValue(mockResponse(true, 200, 'OK'));
|
|
272
|
+
vi.stubGlobal('fetch', mockFetch);
|
|
273
|
+
const logDelivery = vi.fn().mockResolvedValue(undefined);
|
|
274
|
+
const webhook = makeWebhook({ id: 'wh-log' });
|
|
275
|
+
triggerWebhooks([webhook], 'click_event', 'evt-3', {}, logDelivery);
|
|
276
|
+
await vi.runAllTimersAsync();
|
|
277
|
+
expect(logDelivery).toHaveBeenCalledWith('wh-log', expect.objectContaining({ success: true }));
|
|
278
|
+
});
|
|
279
|
+
it('includes the timestamp in the payload', async () => {
|
|
280
|
+
const mockFetch = vi.fn().mockResolvedValue(mockResponse(true, 200, 'OK'));
|
|
281
|
+
vi.stubGlobal('fetch', mockFetch);
|
|
282
|
+
triggerWebhooks([makeWebhook()], 'click_event', 'evt-4', {});
|
|
283
|
+
await vi.runAllTimersAsync();
|
|
284
|
+
const body = JSON.parse(mockFetch.mock.calls[0][1].body);
|
|
285
|
+
expect(body.timestamp).toBeDefined();
|
|
286
|
+
expect(new Date(body.timestamp).toISOString()).toBe(body.timestamp);
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
//# sourceMappingURL=webhook.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook.test.js","sourceRoot":"","sources":["../../src/lib/webhook.test.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;
|
|
1
|
+
{"version":3,"file":"redirect.d.ts","sourceRoot":"","sources":["../../src/routes/redirect.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAkE1C,wBAAsB,cAAc,CAAC,OAAO,EAAE,eAAe,iBAob5D"}
|
package/dist/routes/redirect.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { db } from '../lib/database.js';
|
|
2
|
+
import { getClientIp } from '../lib/client-ip.js';
|
|
2
3
|
import { parseUserAgent, getLocationFromIP, buildRedirectUrl, detectDevice } from '../lib/utils.js';
|
|
3
4
|
import { storeFingerprintForClick } from '../lib/fingerprint.js';
|
|
4
5
|
import { emitClickEvent } from '../lib/event-emitter.js';
|
|
@@ -79,9 +80,12 @@ export async function redirectRoutes(fastify) {
|
|
|
79
80
|
let params;
|
|
80
81
|
if (templateSlug) {
|
|
81
82
|
// Template-based URL: verify both template and link match
|
|
83
|
+
// Also fetch template settings and org settings for URL fallback chain
|
|
82
84
|
query = `
|
|
83
|
-
SELECT l
|
|
85
|
+
SELECT l.*, t.settings AS template_settings, o.settings AS org_settings
|
|
86
|
+
FROM links l
|
|
84
87
|
LEFT JOIN link_templates t ON l.template_id = t.id
|
|
88
|
+
LEFT JOIN organizations o ON l.organization_id = o.id
|
|
85
89
|
WHERE l.short_code = $1 AND t.slug = $2
|
|
86
90
|
AND l.is_active = true
|
|
87
91
|
AND (l.expires_at IS NULL OR l.expires_at > NOW())
|
|
@@ -90,10 +94,14 @@ export async function redirectRoutes(fastify) {
|
|
|
90
94
|
}
|
|
91
95
|
else {
|
|
92
96
|
// Legacy URL: just lookup by short code
|
|
97
|
+
// Also fetch template settings and org settings for URL fallback chain
|
|
93
98
|
query = `
|
|
94
|
-
SELECT
|
|
95
|
-
|
|
96
|
-
|
|
99
|
+
SELECT l.*, t.settings AS template_settings, o.settings AS org_settings
|
|
100
|
+
FROM links l
|
|
101
|
+
LEFT JOIN link_templates t ON l.template_id = t.id
|
|
102
|
+
LEFT JOIN organizations o ON l.organization_id = o.id
|
|
103
|
+
WHERE l.short_code = $1 AND l.is_active = true
|
|
104
|
+
AND (l.expires_at IS NULL OR l.expires_at > NOW())
|
|
97
105
|
`;
|
|
98
106
|
params = [shortCode];
|
|
99
107
|
}
|
|
@@ -116,7 +124,7 @@ export async function redirectRoutes(fastify) {
|
|
|
116
124
|
// Check targeting rules BEFORE redirecting
|
|
117
125
|
if (link.targeting_rules) {
|
|
118
126
|
const userAgent = request.headers['user-agent'] || '';
|
|
119
|
-
const ip = request
|
|
127
|
+
const ip = getClientIp(request);
|
|
120
128
|
const acceptLanguage = request.headers['accept-language'] || '';
|
|
121
129
|
// Get user's actual data for targeting checks
|
|
122
130
|
const device = detectDevice(userAgent);
|
|
@@ -154,7 +162,7 @@ export async function redirectRoutes(fastify) {
|
|
|
154
162
|
setImmediate(async () => {
|
|
155
163
|
try {
|
|
156
164
|
const userAgent = request.headers['user-agent'] || '';
|
|
157
|
-
const ip = request
|
|
165
|
+
const ip = getClientIp(request);
|
|
158
166
|
const referrer = request.headers.referer || null;
|
|
159
167
|
const acceptLanguage = request.headers['accept-language'] || '';
|
|
160
168
|
const deviceType = detectDevice(userAgent);
|
|
@@ -208,6 +216,13 @@ export async function redirectRoutes(fastify) {
|
|
|
208
216
|
};
|
|
209
217
|
await storeFingerprintForClick(clickId, fingerprintData);
|
|
210
218
|
// Determine redirect URL for event emission (using same logic as main redirect)
|
|
219
|
+
// Use the same fallback chain: link → template → workspace
|
|
220
|
+
const tplSettings = link.template_settings || {};
|
|
221
|
+
const oSettings = link.org_settings || {};
|
|
222
|
+
const oAppConfig = oSettings.appConfig || {};
|
|
223
|
+
const iosStoreUrl = link.ios_app_store_url || tplSettings.defaultIosUrl || oAppConfig.iosAppStoreUrl || null;
|
|
224
|
+
const androidStoreUrl = link.android_app_store_url || tplSettings.defaultAndroidUrl || oAppConfig.androidAppStoreUrl || null;
|
|
225
|
+
const webFallback = link.web_fallback_url || tplSettings.defaultWebFallbackUrl || oAppConfig.webFallbackUrl || null;
|
|
211
226
|
let redirectUrl = link.original_url;
|
|
212
227
|
let redirectReason = 'original_url';
|
|
213
228
|
if (deviceType === 'ios') {
|
|
@@ -219,10 +234,14 @@ export async function redirectRoutes(fastify) {
|
|
|
219
234
|
redirectUrl = `${link.app_scheme}://${link.deep_link_path.replace(/^\//, '')}`;
|
|
220
235
|
redirectReason = 'app_scheme';
|
|
221
236
|
}
|
|
222
|
-
else if (
|
|
223
|
-
redirectUrl =
|
|
237
|
+
else if (iosStoreUrl) {
|
|
238
|
+
redirectUrl = iosStoreUrl;
|
|
224
239
|
redirectReason = 'ios_app_store_url';
|
|
225
240
|
}
|
|
241
|
+
else if (webFallback) {
|
|
242
|
+
redirectUrl = webFallback;
|
|
243
|
+
redirectReason = 'web_fallback_url';
|
|
244
|
+
}
|
|
226
245
|
}
|
|
227
246
|
else if (deviceType === 'android') {
|
|
228
247
|
if (link.android_app_link) {
|
|
@@ -233,16 +252,20 @@ export async function redirectRoutes(fastify) {
|
|
|
233
252
|
redirectUrl = `${link.app_scheme}://${link.deep_link_path.replace(/^\//, '')}`;
|
|
234
253
|
redirectReason = 'app_scheme';
|
|
235
254
|
}
|
|
236
|
-
else if (
|
|
237
|
-
redirectUrl =
|
|
255
|
+
else if (androidStoreUrl) {
|
|
256
|
+
redirectUrl = androidStoreUrl;
|
|
238
257
|
redirectReason = 'android_app_store_url';
|
|
239
258
|
}
|
|
259
|
+
else if (webFallback) {
|
|
260
|
+
redirectUrl = webFallback;
|
|
261
|
+
redirectReason = 'web_fallback_url';
|
|
262
|
+
}
|
|
240
263
|
}
|
|
241
|
-
else if (deviceType === 'web' &&
|
|
242
|
-
redirectUrl =
|
|
264
|
+
else if (deviceType === 'web' && webFallback) {
|
|
265
|
+
redirectUrl = webFallback;
|
|
243
266
|
redirectReason = 'web_fallback_url';
|
|
244
267
|
}
|
|
245
|
-
const finalRedirectUrl = buildRedirectUrl(redirectUrl, link.utm_parameters);
|
|
268
|
+
const finalRedirectUrl = buildRedirectUrl(redirectUrl, link.utm_parameters) || redirectUrl;
|
|
246
269
|
// Emit click event for real-time streaming to WebSocket clients
|
|
247
270
|
emitClickEvent({
|
|
248
271
|
eventId: clickId,
|
|
@@ -302,16 +325,26 @@ export async function redirectRoutes(fastify) {
|
|
|
302
325
|
}
|
|
303
326
|
});
|
|
304
327
|
// Determine redirect URL based on device with smart fallback chain
|
|
328
|
+
// Fallback chain: link URLs → template default URLs → workspace settings URLs
|
|
305
329
|
const userAgent = request.headers['user-agent'] || '';
|
|
306
330
|
const device = detectDevice(userAgent);
|
|
331
|
+
// Extract fallback URLs from template settings and org settings
|
|
332
|
+
const templateSettings = link.template_settings || {};
|
|
333
|
+
const orgSettings = link.org_settings || {};
|
|
334
|
+
const orgAppConfig = orgSettings.appConfig || {};
|
|
335
|
+
// Resolve platform URLs with fallback chain: link → template → workspace
|
|
336
|
+
const iosUrl = link.ios_app_store_url || templateSettings.defaultIosUrl || orgAppConfig.iosAppStoreUrl || null;
|
|
337
|
+
const androidUrl = link.android_app_store_url || templateSettings.defaultAndroidUrl || orgAppConfig.androidAppStoreUrl || null;
|
|
338
|
+
const webFallbackUrl = link.web_fallback_url || templateSettings.defaultWebFallbackUrl || orgAppConfig.webFallbackUrl || null;
|
|
307
339
|
let redirectUrl = link.original_url;
|
|
308
340
|
let useSchemeUrl = false; // Track if we're using a URI scheme URL
|
|
309
341
|
if (device === 'ios') {
|
|
310
342
|
// iOS Priority:
|
|
311
343
|
// 1. Universal Link (HTTPS URL with AASA file) - if app installed, opens app
|
|
312
344
|
// 2. URI scheme (myapp://path) - fallback when Universal Links fail
|
|
313
|
-
// 3. App Store URL - for users who don't have the app
|
|
314
|
-
// 4.
|
|
345
|
+
// 3. App Store URL (link → template → workspace) - for users who don't have the app
|
|
346
|
+
// 4. Web fallback URL - browser-based fallback
|
|
347
|
+
// 5. Original URL - ultimate fallback
|
|
315
348
|
if (link.ios_universal_link) {
|
|
316
349
|
redirectUrl = link.ios_universal_link;
|
|
317
350
|
}
|
|
@@ -320,16 +353,20 @@ export async function redirectRoutes(fastify) {
|
|
|
320
353
|
redirectUrl = `${link.app_scheme}://${link.deep_link_path.replace(/^\//, '')}`;
|
|
321
354
|
useSchemeUrl = true;
|
|
322
355
|
}
|
|
323
|
-
else if (
|
|
324
|
-
redirectUrl =
|
|
356
|
+
else if (iosUrl) {
|
|
357
|
+
redirectUrl = iosUrl;
|
|
358
|
+
}
|
|
359
|
+
else if (webFallbackUrl) {
|
|
360
|
+
redirectUrl = webFallbackUrl;
|
|
325
361
|
}
|
|
326
362
|
}
|
|
327
363
|
else if (device === 'android') {
|
|
328
364
|
// Android Priority:
|
|
329
365
|
// 1. App Link (HTTPS URL with Digital Asset Links) - if app installed, opens app
|
|
330
366
|
// 2. URI scheme (myapp://path) - fallback when App Links fail
|
|
331
|
-
// 3. Play Store URL - for users who don't have the app
|
|
332
|
-
// 4.
|
|
367
|
+
// 3. Play Store URL (link → template → workspace) - for users who don't have the app
|
|
368
|
+
// 4. Web fallback URL - browser-based fallback
|
|
369
|
+
// 5. Original URL - ultimate fallback
|
|
333
370
|
if (link.android_app_link) {
|
|
334
371
|
redirectUrl = link.android_app_link;
|
|
335
372
|
}
|
|
@@ -338,19 +375,26 @@ export async function redirectRoutes(fastify) {
|
|
|
338
375
|
redirectUrl = `${link.app_scheme}://${link.deep_link_path.replace(/^\//, '')}`;
|
|
339
376
|
useSchemeUrl = true;
|
|
340
377
|
}
|
|
341
|
-
else if (
|
|
342
|
-
redirectUrl =
|
|
378
|
+
else if (androidUrl) {
|
|
379
|
+
redirectUrl = androidUrl;
|
|
380
|
+
}
|
|
381
|
+
else if (webFallbackUrl) {
|
|
382
|
+
redirectUrl = webFallbackUrl;
|
|
343
383
|
}
|
|
344
384
|
}
|
|
345
385
|
else if (device === 'web') {
|
|
346
386
|
// Web fallback
|
|
347
|
-
redirectUrl =
|
|
387
|
+
redirectUrl = webFallbackUrl || link.original_url;
|
|
388
|
+
}
|
|
389
|
+
// If no URL found at all, return a user-friendly error
|
|
390
|
+
if (!redirectUrl) {
|
|
391
|
+
return reply.status(404).send({ error: 'No destination URL configured for this link' });
|
|
348
392
|
}
|
|
349
393
|
// Build final URL with parameters
|
|
350
394
|
let finalUrl = redirectUrl;
|
|
351
395
|
if (!useSchemeUrl) {
|
|
352
396
|
// For HTTP(S) URLs, add UTM parameters
|
|
353
|
-
finalUrl = buildRedirectUrl(redirectUrl, link.utm_parameters);
|
|
397
|
+
finalUrl = buildRedirectUrl(redirectUrl, link.utm_parameters) || redirectUrl;
|
|
354
398
|
// Add deep link parameters as query params
|
|
355
399
|
if (link.deep_link_parameters && Object.keys(link.deep_link_parameters).length > 0) {
|
|
356
400
|
try {
|
|
@@ -387,7 +431,7 @@ export async function redirectRoutes(fastify) {
|
|
|
387
431
|
const params = new URLSearchParams(Object.entries(link.deep_link_parameters).map(([k, v]) => [k, String(v)]));
|
|
388
432
|
fullSchemeUrl += (fullSchemeUrl.includes('?') ? '&' : '?') + params.toString();
|
|
389
433
|
}
|
|
390
|
-
const storeFallback =
|
|
434
|
+
const storeFallback = iosUrl || webFallbackUrl || link.original_url;
|
|
391
435
|
return reply
|
|
392
436
|
.header('Content-Type', 'text/html; charset=utf-8')
|
|
393
437
|
.send(generateInterstitialHTML(fullSchemeUrl, storeFallback, link.title || link.og_title));
|