@linkforty/core 1.44.0 → 1.44.2

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 (174) hide show
  1. package/README.md +6 -6
  2. package/package.json +2 -1
  3. package/dist/index.config.test.d.ts +0 -2
  4. package/dist/index.config.test.d.ts.map +0 -1
  5. package/dist/index.config.test.js +0 -87
  6. package/dist/index.config.test.js.map +0 -1
  7. package/dist/lib/bot-detection.test.d.ts +0 -2
  8. package/dist/lib/bot-detection.test.d.ts.map +0 -1
  9. package/dist/lib/bot-detection.test.js +0 -65
  10. package/dist/lib/bot-detection.test.js.map +0 -1
  11. package/dist/lib/client-ip.test.d.ts +0 -7
  12. package/dist/lib/client-ip.test.d.ts.map +0 -1
  13. package/dist/lib/client-ip.test.js +0 -114
  14. package/dist/lib/client-ip.test.js.map +0 -1
  15. package/dist/lib/config.test.d.ts +0 -2
  16. package/dist/lib/config.test.d.ts.map +0 -1
  17. package/dist/lib/config.test.js +0 -126
  18. package/dist/lib/config.test.js.map +0 -1
  19. package/dist/lib/database.test.d.ts +0 -2
  20. package/dist/lib/database.test.d.ts.map +0 -1
  21. package/dist/lib/database.test.js +0 -180
  22. package/dist/lib/database.test.js.map +0 -1
  23. package/dist/lib/deep-link-payload.test.d.ts +0 -2
  24. package/dist/lib/deep-link-payload.test.d.ts.map +0 -1
  25. package/dist/lib/deep-link-payload.test.js +0 -202
  26. package/dist/lib/deep-link-payload.test.js.map +0 -1
  27. package/dist/lib/event-emitter.test.d.ts +0 -2
  28. package/dist/lib/event-emitter.test.d.ts.map +0 -1
  29. package/dist/lib/event-emitter.test.js +0 -162
  30. package/dist/lib/event-emitter.test.js.map +0 -1
  31. package/dist/lib/fingerprint.test.d.ts +0 -2
  32. package/dist/lib/fingerprint.test.d.ts.map +0 -1
  33. package/dist/lib/fingerprint.test.js +0 -750
  34. package/dist/lib/fingerprint.test.js.map +0 -1
  35. package/dist/lib/launchpad.test.d.ts +0 -2
  36. package/dist/lib/launchpad.test.d.ts.map +0 -1
  37. package/dist/lib/launchpad.test.js +0 -368
  38. package/dist/lib/launchpad.test.js.map +0 -1
  39. package/dist/lib/link-lookup.test.d.ts +0 -2
  40. package/dist/lib/link-lookup.test.d.ts.map +0 -1
  41. package/dist/lib/link-lookup.test.js +0 -61
  42. package/dist/lib/link-lookup.test.js.map +0 -1
  43. package/dist/lib/link-resolution-cache.test.d.ts +0 -2
  44. package/dist/lib/link-resolution-cache.test.d.ts.map +0 -1
  45. package/dist/lib/link-resolution-cache.test.js +0 -45
  46. package/dist/lib/link-resolution-cache.test.js.map +0 -1
  47. package/dist/lib/link-safety.test.d.ts +0 -2
  48. package/dist/lib/link-safety.test.d.ts.map +0 -1
  49. package/dist/lib/link-safety.test.js +0 -211
  50. package/dist/lib/link-safety.test.js.map +0 -1
  51. package/dist/lib/not-found-page.test.d.ts +0 -2
  52. package/dist/lib/not-found-page.test.d.ts.map +0 -1
  53. package/dist/lib/not-found-page.test.js +0 -151
  54. package/dist/lib/not-found-page.test.js.map +0 -1
  55. package/dist/lib/portable/cli.test.d.ts +0 -2
  56. package/dist/lib/portable/cli.test.d.ts.map +0 -1
  57. package/dist/lib/portable/cli.test.js +0 -98
  58. package/dist/lib/portable/cli.test.js.map +0 -1
  59. package/dist/lib/portable/portable.integration.test.d.ts +0 -2
  60. package/dist/lib/portable/portable.integration.test.d.ts.map +0 -1
  61. package/dist/lib/portable/portable.integration.test.js +0 -139
  62. package/dist/lib/portable/portable.integration.test.js.map +0 -1
  63. package/dist/lib/portable/registry.schema.test.d.ts +0 -2
  64. package/dist/lib/portable/registry.schema.test.d.ts.map +0 -1
  65. package/dist/lib/portable/registry.schema.test.js +0 -59
  66. package/dist/lib/portable/registry.schema.test.js.map +0 -1
  67. package/dist/lib/scanner-detection.test.d.ts +0 -2
  68. package/dist/lib/scanner-detection.test.d.ts.map +0 -1
  69. package/dist/lib/scanner-detection.test.js +0 -124
  70. package/dist/lib/scanner-detection.test.js.map +0 -1
  71. package/dist/lib/schema-guard.test.d.ts +0 -2
  72. package/dist/lib/schema-guard.test.d.ts.map +0 -1
  73. package/dist/lib/schema-guard.test.js +0 -83
  74. package/dist/lib/schema-guard.test.js.map +0 -1
  75. package/dist/lib/utils.test.d.ts +0 -2
  76. package/dist/lib/utils.test.d.ts.map +0 -1
  77. package/dist/lib/utils.test.js +0 -261
  78. package/dist/lib/utils.test.js.map +0 -1
  79. package/dist/lib/webhook.test.d.ts +0 -2
  80. package/dist/lib/webhook.test.d.ts.map +0 -1
  81. package/dist/lib/webhook.test.js +0 -340
  82. package/dist/lib/webhook.test.js.map +0 -1
  83. package/dist/routes/health.test.d.ts +0 -2
  84. package/dist/routes/health.test.d.ts.map +0 -1
  85. package/dist/routes/health.test.js +0 -82
  86. package/dist/routes/health.test.js.map +0 -1
  87. package/dist/routes/preview.test.d.ts +0 -2
  88. package/dist/routes/preview.test.d.ts.map +0 -1
  89. package/dist/routes/preview.test.js +0 -111
  90. package/dist/routes/preview.test.js.map +0 -1
  91. package/dist/routes/qr.test.d.ts +0 -2
  92. package/dist/routes/qr.test.d.ts.map +0 -1
  93. package/dist/routes/qr.test.js +0 -67
  94. package/dist/routes/qr.test.js.map +0 -1
  95. package/dist/routes/redirect.click-event.test.d.ts +0 -2
  96. package/dist/routes/redirect.click-event.test.d.ts.map +0 -1
  97. package/dist/routes/redirect.click-event.test.js +0 -85
  98. package/dist/routes/redirect.click-event.test.js.map +0 -1
  99. package/dist/routes/redirect.host-scope.integration.test.d.ts +0 -2
  100. package/dist/routes/redirect.host-scope.integration.test.d.ts.map +0 -1
  101. package/dist/routes/redirect.host-scope.integration.test.js +0 -105
  102. package/dist/routes/redirect.host-scope.integration.test.js.map +0 -1
  103. package/dist/routes/redirect.interstitial.test.d.ts +0 -2
  104. package/dist/routes/redirect.interstitial.test.d.ts.map +0 -1
  105. package/dist/routes/redirect.interstitial.test.js +0 -128
  106. package/dist/routes/redirect.interstitial.test.js.map +0 -1
  107. package/dist/routes/redirect.launchpad.test.d.ts +0 -2
  108. package/dist/routes/redirect.launchpad.test.d.ts.map +0 -1
  109. package/dist/routes/redirect.launchpad.test.js +0 -515
  110. package/dist/routes/redirect.launchpad.test.js.map +0 -1
  111. package/dist/routes/redirect.no-web-destination.test.d.ts +0 -2
  112. package/dist/routes/redirect.no-web-destination.test.d.ts.map +0 -1
  113. package/dist/routes/redirect.no-web-destination.test.js +0 -176
  114. package/dist/routes/redirect.no-web-destination.test.js.map +0 -1
  115. package/dist/routes/redirect.not-found.test.d.ts +0 -2
  116. package/dist/routes/redirect.not-found.test.d.ts.map +0 -1
  117. package/dist/routes/redirect.not-found.test.js +0 -280
  118. package/dist/routes/redirect.not-found.test.js.map +0 -1
  119. package/dist/routes/redirect.safety.test.d.ts +0 -2
  120. package/dist/routes/redirect.safety.test.d.ts.map +0 -1
  121. package/dist/routes/redirect.safety.test.js +0 -284
  122. package/dist/routes/redirect.safety.test.js.map +0 -1
  123. package/dist/routes/redirect.store-url.test.d.ts +0 -2
  124. package/dist/routes/redirect.store-url.test.d.ts.map +0 -1
  125. package/dist/routes/redirect.store-url.test.js +0 -239
  126. package/dist/routes/redirect.store-url.test.js.map +0 -1
  127. package/dist/routes/redirect.test.d.ts +0 -2
  128. package/dist/routes/redirect.test.d.ts.map +0 -1
  129. package/dist/routes/redirect.test.js +0 -158
  130. package/dist/routes/redirect.test.js.map +0 -1
  131. package/dist/routes/redirect.web-link.test.d.ts +0 -2
  132. package/dist/routes/redirect.web-link.test.d.ts.map +0 -1
  133. package/dist/routes/redirect.web-link.test.js +0 -138
  134. package/dist/routes/redirect.web-link.test.js.map +0 -1
  135. package/dist/routes/sdk-cache-bypass.test.d.ts +0 -2
  136. package/dist/routes/sdk-cache-bypass.test.d.ts.map +0 -1
  137. package/dist/routes/sdk-cache-bypass.test.js +0 -175
  138. package/dist/routes/sdk-cache-bypass.test.js.map +0 -1
  139. package/dist/routes/sdk.device-values.integration.test.d.ts +0 -2
  140. package/dist/routes/sdk.device-values.integration.test.d.ts.map +0 -1
  141. package/dist/routes/sdk.device-values.integration.test.js +0 -132
  142. package/dist/routes/sdk.device-values.integration.test.js.map +0 -1
  143. package/dist/routes/sdk.event.test.d.ts +0 -2
  144. package/dist/routes/sdk.event.test.d.ts.map +0 -1
  145. package/dist/routes/sdk.event.test.js +0 -284
  146. package/dist/routes/sdk.event.test.js.map +0 -1
  147. package/dist/routes/sdk.install-certainty.integration.test.d.ts +0 -2
  148. package/dist/routes/sdk.install-certainty.integration.test.d.ts.map +0 -1
  149. package/dist/routes/sdk.install-certainty.integration.test.js +0 -104
  150. package/dist/routes/sdk.install-certainty.integration.test.js.map +0 -1
  151. package/dist/routes/sdk.install-live-links.integration.test.d.ts +0 -2
  152. package/dist/routes/sdk.install-live-links.integration.test.d.ts.map +0 -1
  153. package/dist/routes/sdk.install-live-links.integration.test.js +0 -92
  154. package/dist/routes/sdk.install-live-links.integration.test.js.map +0 -1
  155. package/dist/routes/sdk.install-referrer.integration.test.d.ts +0 -2
  156. package/dist/routes/sdk.install-referrer.integration.test.d.ts.map +0 -1
  157. package/dist/routes/sdk.install-referrer.integration.test.js +0 -187
  158. package/dist/routes/sdk.install-referrer.integration.test.js.map +0 -1
  159. package/dist/routes/sdk.install-window-once.integration.test.d.ts +0 -2
  160. package/dist/routes/sdk.install-window-once.integration.test.d.ts.map +0 -1
  161. package/dist/routes/sdk.install-window-once.integration.test.js +0 -147
  162. package/dist/routes/sdk.install-window-once.integration.test.js.map +0 -1
  163. package/dist/routes/sdk.install.test.d.ts +0 -2
  164. package/dist/routes/sdk.install.test.d.ts.map +0 -1
  165. package/dist/routes/sdk.install.test.js +0 -99
  166. package/dist/routes/sdk.install.test.js.map +0 -1
  167. package/dist/routes/sdk.resolve-click.test.d.ts +0 -2
  168. package/dist/routes/sdk.resolve-click.test.d.ts.map +0 -1
  169. package/dist/routes/sdk.resolve-click.test.js +0 -79
  170. package/dist/routes/sdk.resolve-click.test.js.map +0 -1
  171. package/dist/routes/sdk.resolve-scope.test.d.ts +0 -2
  172. package/dist/routes/sdk.resolve-scope.test.d.ts.map +0 -1
  173. package/dist/routes/sdk.resolve-scope.test.js +0 -105
  174. package/dist/routes/sdk.resolve-scope.test.js.map +0 -1
@@ -1,92 +0,0 @@
1
- import pg from 'pg';
2
- import Fastify from 'fastify';
3
- import { afterAll, beforeAll, describe, expect, it } from 'vitest';
4
- import { db, initializeDatabase } from '../lib/database.js';
5
- import { sdkRoutes } from './sdk.js';
6
- /**
7
- * An install is matched only to a click on a link that would still open: not
8
- * one that has expired, been deactivated or been disabled since the click.
9
- *
10
- * Each case clicks from its own /24, so it can only match its own link.
11
- * Needs a real database; skipped without DATABASE_URL, like database.test.ts.
12
- */
13
- const ADMIN_URL = process.env.DATABASE_URL;
14
- const clickUserAgent = 'Mozilla/5.0 (Linux; Android 15; Pixel 8) AppleWebKit/537.36 Chrome/130.0 Mobile Safari/537.36';
15
- const installBody = {
16
- userAgent: 'App/1.0 Android/15',
17
- timezone: 'Europe/Madrid',
18
- language: 'en-ES',
19
- screenWidth: 1080,
20
- screenHeight: 2400,
21
- platform: 'android',
22
- platformVersion: '15',
23
- };
24
- describe.skipIf(!ADMIN_URL)('install matching skips links that no longer open (integration)', () => {
25
- const dbName = `lf_core_live_links_${Date.now()}`;
26
- let admin;
27
- let app;
28
- beforeAll(async () => {
29
- admin = new pg.Client({ connectionString: ADMIN_URL });
30
- await admin.connect();
31
- await admin.query(`CREATE DATABASE ${dbName}`);
32
- const url = new URL(ADMIN_URL);
33
- url.pathname = `/${dbName}`;
34
- await initializeDatabase({ url: url.toString(), pool: { min: 1, max: 3 } });
35
- app = Fastify();
36
- await app.register(sdkRoutes);
37
- await app.ready();
38
- }, 120000);
39
- afterAll(async () => {
40
- await app?.close();
41
- await db.end().catch(() => undefined);
42
- await admin.query(`DROP DATABASE IF EXISTS ${dbName} WITH (FORCE)`);
43
- await admin.end();
44
- }, 60000);
45
- /**
46
- * A link with one click from `network`.1, ten minutes ago. `columns` are SQL
47
- * expressions, so times come from the database clock like the matcher's NOW().
48
- */
49
- async function clickedLink(code, network, columns = {}) {
50
- const link = await db.query(`INSERT INTO links (short_code, original_url${Object.keys(columns).map((c) => `, ${c}`).join('')})
51
- VALUES ($1, 'https://example.com/landing'${Object.values(columns).map((v) => `, ${v}`).join('')})
52
- RETURNING id`, [code]);
53
- const click = await db.query(`INSERT INTO click_events (link_id, clicked_at, ip_address, user_agent, platform)
54
- VALUES ($1, NOW() - INTERVAL '10 minutes', $2, $3, 'android') RETURNING id`, [link.rows[0].id, `${network}.1`, clickUserAgent]);
55
- await db.query(`INSERT INTO device_fingerprints
56
- (click_id, fingerprint_hash, ip_address, user_agent, timezone, language, screen_width, screen_height, platform, platform_version)
57
- VALUES ($1, 'h', $2, $3, 'Europe/Madrid', 'en-ES', 412, 915, 'android', '15')`, [click.rows[0].id, `${network}.1`, clickUserAgent]);
58
- }
59
- async function installFrom(network) {
60
- const res = await app.inject({
61
- method: 'POST',
62
- url: '/api/sdk/v1/install',
63
- payload: installBody,
64
- remoteAddress: `${network}.2`,
65
- });
66
- expect(res.statusCode).toBe(200);
67
- return res.json();
68
- }
69
- it('matches a click on a link that still opens', async () => {
70
- await clickedLink('livelink', '81.2.69');
71
- const body = await installFrom('81.2.69');
72
- expect(body.attributed).toBe(true);
73
- expect(body.deepLinkData.shortCode).toBe('livelink');
74
- });
75
- it('does not match a link that expired after the click', async () => {
76
- await clickedLink('expired', '81.2.70', { expires_at: "NOW() - INTERVAL '1 minute'" });
77
- expect((await installFrom('81.2.70')).attributed).toBe(false);
78
- });
79
- it('matches a link that expires later', async () => {
80
- await clickedLink('notyet', '81.2.71', { expires_at: "NOW() + INTERVAL '1 hour'" });
81
- expect((await installFrom('81.2.71')).attributed).toBe(true);
82
- });
83
- it('does not match an inactive link', async () => {
84
- await clickedLink('inactive', '81.2.72', { is_active: 'false' });
85
- expect((await installFrom('81.2.72')).attributed).toBe(false);
86
- });
87
- it('does not match a disabled link', async () => {
88
- await clickedLink('disabled', '81.2.73', { disabled_at: 'NOW()' });
89
- expect((await installFrom('81.2.73')).attributed).toBe(false);
90
- });
91
- });
92
- //# sourceMappingURL=sdk.install-live-links.integration.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"sdk.install-live-links.integration.test.js","sourceRoot":"","sources":["../../src/routes/sdk.install-live-links.integration.test.ts"],"names":[],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=sdk.install-referrer.integration.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"sdk.install-referrer.integration.test.d.ts","sourceRoot":"","sources":["../../src/routes/sdk.install-referrer.integration.test.ts"],"names":[],"mappings":""}
@@ -1,187 +0,0 @@
1
- import pg from 'pg';
2
- import Fastify from 'fastify';
3
- import { afterAll, beforeAll, describe, expect, it } from 'vitest';
4
- import { db, initializeDatabase } from '../lib/database.js';
5
- import { linkRoutes } from './links.js';
6
- import { redirectRoutes } from './redirect.js';
7
- import { sdkRoutes } from './sdk.js';
8
- /**
9
- * An install that sends the Google Play install referrer is matched to the
10
- * click the referrer names, under the same rules as a fingerprint match, and
11
- * a referrer without a click id leaves the install to its fingerprint.
12
- *
13
- * Each case uses its own /24, so a fingerprint can only match that case's
14
- * clicks. Needs a real database; skipped without DATABASE_URL, like
15
- * database.test.ts.
16
- */
17
- const ADMIN_URL = process.env.DATABASE_URL;
18
- const clickUserAgent = 'Mozilla/5.0 (Linux; Android 15; Pixel 8) AppleWebKit/537.36 Chrome/130.0 Mobile Safari/537.36';
19
- const installBody = {
20
- userAgent: 'App/1.0 Android/15',
21
- timezone: 'Europe/Madrid',
22
- language: 'en-ES',
23
- screenWidth: 1080,
24
- screenHeight: 2400,
25
- platform: 'android',
26
- platformVersion: '15',
27
- };
28
- const UNKNOWN_CLICK = '00000000-0000-4000-8000-000000000000';
29
- describe.skipIf(!ADMIN_URL)('install referrer matching (integration)', () => {
30
- const dbName = `lf_core_referrer_${Date.now()}`;
31
- let admin;
32
- let app;
33
- let scopedApp;
34
- beforeAll(async () => {
35
- admin = new pg.Client({ connectionString: ADMIN_URL });
36
- await admin.connect();
37
- await admin.query(`CREATE DATABASE ${dbName}`);
38
- const url = new URL(ADMIN_URL);
39
- url.pathname = `/${dbName}`;
40
- await initializeDatabase({ url: url.toString(), pool: { min: 1, max: 3 } });
41
- app = Fastify();
42
- await app.register(linkRoutes);
43
- await app.register(sdkRoutes);
44
- await app.register(redirectRoutes);
45
- await app.ready();
46
- // A deployment serving several apps: the app token names the organization.
47
- scopedApp = Fastify();
48
- const scoped = {
49
- resolveInstallScope: (_request, { appToken }) => (appToken ? { organizationId: appToken } : { matchNothing: true }),
50
- };
51
- await scopedApp.register(sdkRoutes, scoped);
52
- await scopedApp.ready();
53
- }, 120000);
54
- afterAll(async () => {
55
- await app?.close();
56
- await scopedApp?.close();
57
- await db.end().catch(() => undefined);
58
- await admin.query(`DROP DATABASE IF EXISTS ${dbName} WITH (FORCE)`);
59
- await admin.end();
60
- }, 60000);
61
- async function createLink(body = {}) {
62
- const res = await app.inject({
63
- method: 'POST',
64
- url: '/api/links',
65
- payload: { originalUrl: 'https://example.com/landing', ...body },
66
- });
67
- expect(res.statusCode).toBe(200);
68
- return res.json();
69
- }
70
- /** A click on the link from `network`.1, `minutesAgo` minutes ago by the database clock. */
71
- async function click(linkId, network, minutesAgo) {
72
- const ce = await db.query(`INSERT INTO click_events (link_id, clicked_at, ip_address, user_agent, platform)
73
- VALUES ($1, NOW() - make_interval(mins => $2), $3, $4, 'android') RETURNING id`, [linkId, minutesAgo, `${network}.1`, clickUserAgent]);
74
- await db.query(`INSERT INTO device_fingerprints
75
- (click_id, fingerprint_hash, ip_address, user_agent, timezone, language, screen_width, screen_height, platform, platform_version)
76
- VALUES ($1, 'h', $2, $3, 'Europe/Madrid', 'en-ES', 412, 915, 'android', '15')`, [ce.rows[0].id, `${network}.1`, clickUserAgent]);
77
- return ce.rows[0].id;
78
- }
79
- async function install(network, extra = {}, target = app) {
80
- const res = await target.inject({
81
- method: 'POST',
82
- url: '/api/sdk/v1/install',
83
- payload: { ...installBody, ...extra },
84
- remoteAddress: `${network}.2`,
85
- });
86
- expect(res.statusCode).toBe(200);
87
- return res.json();
88
- }
89
- async function stored(installId) {
90
- const row = await db.query('SELECT link_id, click_id, attribution_method, confidence_score, matched_factors, competing_links FROM install_events WHERE id = $1', [installId]);
91
- return row.rows[0];
92
- }
93
- it('matches the click the Play redirect named, end to end', async () => {
94
- const link = await createLink({ androidAppStoreUrl: 'https://play.google.com/store/apps/details?id=com.example.app' });
95
- const redirect = await app.inject({
96
- method: 'GET',
97
- url: `/${link.short_code}`,
98
- headers: { 'user-agent': clickUserAgent },
99
- remoteAddress: '81.3.10.1',
100
- });
101
- expect(redirect.statusCode).toBe(302);
102
- const referrer = new URL(String(redirect.headers.location)).searchParams.get('referrer');
103
- expect(referrer).toMatch(/^lf_click=[0-9a-f-]{36}$/);
104
- const clickId = new URLSearchParams(referrer ?? '').get('lf_click');
105
- // The click is written after the response.
106
- for (let i = 0; i < 50; i++) {
107
- const found = await db.query('SELECT 1 FROM click_events WHERE id = $1', [clickId]);
108
- if (found.rowCount)
109
- break;
110
- await new Promise((r) => setTimeout(r, 20));
111
- }
112
- // Installed on another network (mobile data, say): no fingerprint could match.
113
- const body = await install('81.3.11', { installReferrer: `${referrer}&utm_source=google-play` });
114
- expect(body).toMatchObject({
115
- attributed: true,
116
- confidenceScore: 100,
117
- matchedFactors: ['install_referrer'],
118
- attributionMethod: 'install_referrer',
119
- competingLinks: 0,
120
- });
121
- expect(body.deepLinkData).toMatchObject({ shortCode: link.short_code, attributionMethod: 'install_referrer', competingLinks: 0 });
122
- expect(await stored(body.installId)).toMatchObject({
123
- link_id: link.id,
124
- click_id: clickId,
125
- attribution_method: 'install_referrer',
126
- matched_factors: ['install_referrer'],
127
- competing_links: 0,
128
- });
129
- });
130
- it('prefers the named click over a fingerprint match on the install network', async () => {
131
- const named = await createLink();
132
- const nearby = await createLink();
133
- const clickId = await click(named.id, '81.3.20', 30);
134
- await click(nearby.id, '81.3.21', 1);
135
- const body = await install('81.3.21', { installReferrer: `lf_click=${clickId}` });
136
- expect(body.attributionMethod).toBe('install_referrer');
137
- expect((await stored(body.installId)).link_id).toBe(named.id);
138
- });
139
- it('gives no match, not a fingerprint guess, when the named click is outside its window', async () => {
140
- const named = await createLink({ attributionWindowMinutes: 10 });
141
- const nearby = await createLink();
142
- const clickId = await click(named.id, '81.3.30', 20);
143
- await click(nearby.id, '81.3.31', 1);
144
- const body = await install('81.3.31', { installReferrer: `lf_click=${clickId}` });
145
- expect(body).toMatchObject({ attributed: false, attributionMethod: 'none', deepLinkData: null });
146
- });
147
- it('gives no match when the named click is on a link that no longer opens', async () => {
148
- const named = await createLink();
149
- const clickId = await click(named.id, '81.3.40', 5);
150
- await db.query('UPDATE links SET is_active = false WHERE id = $1', [named.id]);
151
- const body = await install('81.3.40', { installReferrer: `lf_click=${clickId}` });
152
- expect(body).toMatchObject({ attributed: false, attributionMethod: 'none' });
153
- });
154
- it('hands a one-install click to the first install only', async () => {
155
- const named = await createLink({ oneInstallPerClick: true });
156
- const clickId = await click(named.id, '81.3.50', 5);
157
- const first = await install('81.3.51', { installReferrer: `lf_click=${clickId}` });
158
- expect(first.attributionMethod).toBe('install_referrer');
159
- const second = await install('81.3.52', { installReferrer: `lf_click=${clickId}` });
160
- expect(second).toMatchObject({ attributed: false, attributionMethod: 'none' });
161
- });
162
- it('falls back to the fingerprint for a referrer with no click id, an unknown one, or a non-string', async () => {
163
- const link = await createLink();
164
- await click(link.id, '81.3.60', 5);
165
- for (const installReferrer of ['utm_source=google-play&utm_medium=organic', `lf_click=${UNKNOWN_CLICK}`, 42]) {
166
- const body = await install('81.3.60', { installReferrer });
167
- expect(body).toMatchObject({ attributed: true, attributionMethod: 'fingerprint' });
168
- }
169
- });
170
- it('matches only clicks in the installing app\'s scope', async () => {
171
- const org = await db.query(`INSERT INTO organizations (name) VALUES ('Referrer test') RETURNING id`);
172
- const otherOrg = await db.query(`INSERT INTO organizations (name) VALUES ('Referrer test, other') RETURNING id`);
173
- const mine = await createLink();
174
- const theirs = await createLink();
175
- await db.query('UPDATE links SET organization_id = $1 WHERE id = $2', [org.rows[0].id, mine.id]);
176
- await db.query('UPDATE links SET organization_id = $1 WHERE id = $2', [otherOrg.rows[0].id, theirs.id]);
177
- const myClick = await click(mine.id, '81.3.70', 5);
178
- const theirClick = await click(theirs.id, '81.3.71', 5);
179
- const matched = await install('81.3.72', { installReferrer: `lf_click=${myClick}`, appToken: org.rows[0].id }, scopedApp);
180
- expect(matched.attributionMethod).toBe('install_referrer');
181
- const refused = await install('81.3.73', { installReferrer: `lf_click=${theirClick}`, appToken: org.rows[0].id }, scopedApp);
182
- expect(refused).toMatchObject({ attributed: false, attributionMethod: 'none' });
183
- const tokenless = await install('81.3.74', { installReferrer: `lf_click=${myClick}` }, scopedApp);
184
- expect(tokenless).toMatchObject({ attributed: false, attributionMethod: 'none' });
185
- });
186
- });
187
- //# sourceMappingURL=sdk.install-referrer.integration.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"sdk.install-referrer.integration.test.js","sourceRoot":"","sources":["../../src/routes/sdk.install-referrer.integration.test.ts"],"names":[],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=sdk.install-window-once.integration.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"sdk.install-window-once.integration.test.d.ts","sourceRoot":"","sources":["../../src/routes/sdk.install-window-once.integration.test.ts"],"names":[],"mappings":""}
@@ -1,147 +0,0 @@
1
- import pg from 'pg';
2
- import Fastify from 'fastify';
3
- import { afterAll, beforeAll, describe, expect, it } from 'vitest';
4
- import { db, initializeDatabase } from '../lib/database.js';
5
- import { linkRoutes } from './links.js';
6
- import { sdkRoutes } from './sdk.js';
7
- /**
8
- * A link's install window can be set in minutes, and a link can hand each
9
- * click to one install only.
10
- *
11
- * Each case clicks from its own /24, so it can only match its own link.
12
- * Needs a real database; skipped without DATABASE_URL, like database.test.ts.
13
- */
14
- const ADMIN_URL = process.env.DATABASE_URL;
15
- const clickUserAgent = 'Mozilla/5.0 (Linux; Android 15; Pixel 8) AppleWebKit/537.36 Chrome/130.0 Mobile Safari/537.36';
16
- const installBody = {
17
- userAgent: 'App/1.0 Android/15',
18
- timezone: 'Europe/Madrid',
19
- language: 'en-ES',
20
- screenWidth: 1080,
21
- screenHeight: 2400,
22
- platform: 'android',
23
- platformVersion: '15',
24
- };
25
- describe.skipIf(!ADMIN_URL)('install window in minutes and one install per click (integration)', () => {
26
- const dbName = `lf_core_window_once_${Date.now()}`;
27
- let admin;
28
- let app;
29
- beforeAll(async () => {
30
- admin = new pg.Client({ connectionString: ADMIN_URL });
31
- await admin.connect();
32
- await admin.query(`CREATE DATABASE ${dbName}`);
33
- const url = new URL(ADMIN_URL);
34
- url.pathname = `/${dbName}`;
35
- await initializeDatabase({ url: url.toString(), pool: { min: 1, max: 3 } });
36
- app = Fastify();
37
- await app.register(linkRoutes);
38
- await app.register(sdkRoutes);
39
- await app.ready();
40
- }, 120000);
41
- afterAll(async () => {
42
- await app?.close();
43
- await db.end().catch(() => undefined);
44
- await admin.query(`DROP DATABASE IF EXISTS ${dbName} WITH (FORCE)`);
45
- await admin.end();
46
- }, 60000);
47
- async function createLink(body) {
48
- const res = await app.inject({
49
- method: 'POST',
50
- url: '/api/links',
51
- payload: { originalUrl: 'https://example.com/landing', ...body },
52
- });
53
- expect(res.statusCode).toBe(200);
54
- return res.json();
55
- }
56
- /** A click on the link from `network`.1, `minutesAgo` minutes ago by the database clock. */
57
- async function click(linkId, network, minutesAgo) {
58
- const ce = await db.query(`INSERT INTO click_events (link_id, clicked_at, ip_address, user_agent, platform)
59
- VALUES ($1, NOW() - make_interval(mins => $2), $3, $4, 'android') RETURNING id`, [linkId, minutesAgo, `${network}.1`, clickUserAgent]);
60
- await db.query(`INSERT INTO device_fingerprints
61
- (click_id, fingerprint_hash, ip_address, user_agent, timezone, language, screen_width, screen_height, platform, platform_version)
62
- VALUES ($1, 'h', $2, $3, 'Europe/Madrid', 'en-ES', 412, 915, 'android', '15')`, [ce.rows[0].id, `${network}.1`, clickUserAgent]);
63
- return ce.rows[0].id;
64
- }
65
- async function installFrom(network, host = 2) {
66
- const res = await app.inject({
67
- method: 'POST',
68
- url: '/api/sdk/v1/install',
69
- payload: installBody,
70
- remoteAddress: `${network}.${host}`,
71
- });
72
- expect(res.statusCode).toBe(200);
73
- return res.json();
74
- }
75
- it('matches a click inside a window set in minutes', async () => {
76
- const link = await createLink({ attributionWindowMinutes: 15 });
77
- await click(link.id, '81.2.80', 10);
78
- expect((await installFrom('81.2.80')).attributed).toBe(true);
79
- });
80
- it('does not match a click older than a window set in minutes', async () => {
81
- const link = await createLink({ attributionWindowMinutes: 15 });
82
- await click(link.id, '81.2.81', 20);
83
- expect((await installFrom('81.2.81')).attributed).toBe(false);
84
- });
85
- it('still applies a window set in hours', async () => {
86
- const link = await createLink({ attributionWindowHours: 1 });
87
- await click(link.id, '81.2.82', 50);
88
- expect((await installFrom('81.2.82')).attributed).toBe(true);
89
- const old = await createLink({ attributionWindowHours: 1 });
90
- await click(old.id, '81.2.83', 70);
91
- expect((await installFrom('81.2.83')).attributed).toBe(false);
92
- });
93
- it('replaces a window in minutes when the link is updated with hours', async () => {
94
- const link = await createLink({ attributionWindowMinutes: 5 });
95
- const res = await app.inject({ method: 'PUT', url: `/api/links/${link.id}`, payload: { attributionWindowHours: 2 } });
96
- expect(res.statusCode).toBe(200);
97
- expect(res.json()).toMatchObject({ attribution_window_hours: 2, attribution_window_minutes: null });
98
- });
99
- it('refuses a window in both hours and minutes, and one under five minutes', async () => {
100
- const cases = [
101
- [{ attributionWindowHours: 1, attributionWindowMinutes: 30 }, 'not both'],
102
- [{ attributionWindowMinutes: 4 }, 'at least 5 minutes'],
103
- ];
104
- for (const [body, message] of cases) {
105
- const res = await app.inject({ method: 'POST', url: '/api/links', payload: { originalUrl: 'https://example.com', ...body } });
106
- expect(res.statusCode).not.toBe(200);
107
- expect(res.body).toContain(message);
108
- }
109
- const links = await db.query('SELECT COUNT(*)::int AS n FROM links WHERE original_url = $1', ['https://example.com']);
110
- expect(links.rows[0].n).toBe(0);
111
- });
112
- it('hands a click to one install only when the link says so', async () => {
113
- const link = await createLink({ oneInstallPerClick: true });
114
- expect(link).toMatchObject({ one_install_per_click: true });
115
- const clickId = await click(link.id, '81.2.84', 5);
116
- const first = await installFrom('81.2.84', 2);
117
- expect(first.attributed).toBe(true);
118
- const second = await installFrom('81.2.84', 3);
119
- expect(second.attributed).toBe(false);
120
- const matched = await db.query('SELECT install_matched_at FROM click_events WHERE id = $1', [clickId]);
121
- expect(matched.rows[0].install_matched_at).not.toBeNull();
122
- });
123
- it('gives a second install the next click when the newest is taken', async () => {
124
- const link = await createLink({ oneInstallPerClick: true });
125
- const older = await click(link.id, '81.2.85', 8);
126
- const newer = await click(link.id, '81.2.85', 4);
127
- expect((await installFrom('81.2.85', 2)).attributed).toBe(true);
128
- expect((await installFrom('81.2.85', 3)).attributed).toBe(true);
129
- expect((await installFrom('81.2.85', 4)).attributed).toBe(false);
130
- const credited = await db.query('SELECT click_id FROM install_events WHERE click_id = ANY($1) ORDER BY installed_at', [[older, newer]]);
131
- expect(credited.rows.map((r) => r.click_id)).toEqual([newer, older]);
132
- });
133
- it('lets one click match every install by default', async () => {
134
- const link = await createLink({});
135
- expect(link).toMatchObject({ one_install_per_click: false, attribution_window_minutes: null });
136
- await click(link.id, '81.2.86', 5);
137
- expect((await installFrom('81.2.86', 2)).attributed).toBe(true);
138
- expect((await installFrom('81.2.86', 3)).attributed).toBe(true);
139
- });
140
- it('keeps the setting on a duplicated link', async () => {
141
- const link = await createLink({ oneInstallPerClick: true, attributionWindowMinutes: 30 });
142
- const res = await app.inject({ method: 'POST', url: `/api/links/${link.id}/duplicate` });
143
- expect(res.statusCode).toBe(200);
144
- expect(res.json()).toMatchObject({ one_install_per_click: true, attribution_window_minutes: 30 });
145
- });
146
- });
147
- //# sourceMappingURL=sdk.install-window-once.integration.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"sdk.install-window-once.integration.test.js","sourceRoot":"","sources":["../../src/routes/sdk.install-window-once.integration.test.ts"],"names":[],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=sdk.install.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"sdk.install.test.d.ts","sourceRoot":"","sources":["../../src/routes/sdk.install.test.ts"],"names":[],"mappings":""}
@@ -1,99 +0,0 @@
1
- import { describe, it, expect, beforeEach, vi } from 'vitest';
2
- // Mock the database singleton so the route runs without a real Postgres.
3
- vi.mock('../lib/database.js', () => ({
4
- db: { query: vi.fn() },
5
- }));
6
- import Fastify from 'fastify';
7
- import { db } from '../lib/database.js';
8
- import { sdkRoutes } from './sdk.js';
9
- const mockQuery = db.query;
10
- async function buildApp() {
11
- const app = Fastify();
12
- await app.register(sdkRoutes);
13
- await app.ready();
14
- return app;
15
- }
16
- const installBody = {
17
- userAgent: 'Dalvik/2.1.0 (Linux; U; Android 15; Pixel 8)',
18
- timezone: 'Europe/Madrid',
19
- screenWidth: 1080,
20
- screenHeight: 2400,
21
- platform: 'Android',
22
- platformVersion: '15',
23
- deviceId: 'device-1',
24
- };
25
- describe('POST /api/sdk/v1/install — device values', () => {
26
- beforeEach(() => {
27
- mockQuery.mockReset();
28
- // From a loopback address there is no click to match, so the only query is
29
- // the install INSERT.
30
- mockQuery.mockResolvedValueOnce({ rows: [{ id: 'install-1', deep_link_data: {} }] });
31
- });
32
- it.each([
33
- ['Android locale with regional preferences', 'en-ES-u-fw-mon-mu-celsius', 'en-ES'],
34
- ['iOS locale identifier with a region keyword', 'en_GB@rg=eszzzz', 'en-GB'],
35
- ['iOS locale identifier with a calendar keyword', 'en_US@calendar=japanese', 'en-US'],
36
- ])('records an install from an %s', async (_label, language, stored) => {
37
- const app = await buildApp();
38
- const res = await app.inject({ method: 'POST', url: '/api/sdk/v1/install', payload: { ...installBody, language } });
39
- expect(res.statusCode).toBe(200);
40
- expect(res.json()).toMatchObject({ installId: 'install-1', attributed: false });
41
- const [sql, params] = mockQuery.mock.calls[0];
42
- expect(sql).toMatch(/INSERT INTO install_events/);
43
- expect(params[8]).toBe(stored);
44
- });
45
- it('accepts SDK metadata longer than the stored 50 characters', async () => {
46
- const app = await buildApp();
47
- const res = await app.inject({
48
- method: 'POST',
49
- url: '/api/sdk/v1/install',
50
- payload: { ...installBody, language: 'en-US', sdkName: 'x'.repeat(80), sdkVersion: '1.0.0' },
51
- });
52
- expect(res.statusCode).toBe(200);
53
- expect(mockQuery.mock.calls[0][1][15]).toHaveLength(50);
54
- });
55
- });
56
- describe('POST /api/sdk/v1/install — install scope', () => {
57
- // A public address, so the route looks for candidate clicks unless the scope
58
- // rules it out.
59
- const remoteAddress = '24.5.10.7';
60
- beforeEach(() => {
61
- mockQuery.mockReset();
62
- mockQuery.mockImplementation(async (sql) => /INSERT INTO install_events/.test(sql) ? { rows: [{ id: 'install-1', deep_link_data: {} }] } : { rows: [] });
63
- });
64
- async function installWith(resolveInstallScope) {
65
- const app = Fastify();
66
- await app.register(sdkRoutes, { resolveInstallScope: resolveInstallScope });
67
- await app.ready();
68
- const res = await app.inject({
69
- method: 'POST',
70
- url: '/api/sdk/v1/install',
71
- payload: { ...installBody, appToken: 'token-1' },
72
- remoteAddress,
73
- });
74
- expect(res.statusCode).toBe(200);
75
- return { body: res.json(), sql: mockQuery.mock.calls.map(([q]) => String(q)) };
76
- }
77
- const searchesClicks = (sql) => sql.some((q) => /FROM device_fingerprints/.test(q));
78
- it('searches the scoped organization when the hook names one', async () => {
79
- const { sql } = await installWith(() => ({ organizationId: 'org-1' }));
80
- expect(searchesClicks(sql)).toBe(true);
81
- });
82
- it('searches every click when the hook returns null', async () => {
83
- const { sql } = await installWith(() => null);
84
- expect(searchesClicks(sql)).toBe(true);
85
- });
86
- it('records an organic install without searching when the hook says match nothing', async () => {
87
- const { body, sql } = await installWith(() => ({ matchNothing: true }));
88
- expect(body).toMatchObject({ installId: 'install-1', attributed: false, deepLinkData: null });
89
- expect(searchesClicks(sql)).toBe(false);
90
- });
91
- it('records an organic install without searching when the hook throws', async () => {
92
- const { body, sql } = await installWith(() => {
93
- throw new Error('scope lookup failed');
94
- });
95
- expect(body).toMatchObject({ installId: 'install-1', attributed: false, deepLinkData: null });
96
- expect(searchesClicks(sql)).toBe(false);
97
- });
98
- });
99
- //# sourceMappingURL=sdk.install.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"sdk.install.test.js","sourceRoot":"","sources":["../../src/routes/sdk.install.test.ts"],"names":[],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=sdk.resolve-click.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"sdk.resolve-click.test.d.ts","sourceRoot":"","sources":["../../src/routes/sdk.resolve-click.test.ts"],"names":[],"mappings":""}