@linkforty/core 1.30.0 → 1.31.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.
- package/README.md +12 -5
- package/dist/lib/launchpad.d.ts +22 -1
- package/dist/lib/launchpad.d.ts.map +1 -1
- package/dist/lib/launchpad.js +27 -5
- package/dist/lib/launchpad.js.map +1 -1
- package/dist/lib/launchpad.test.js +98 -0
- package/dist/lib/launchpad.test.js.map +1 -1
- package/dist/lib/store-badges.d.ts +30 -0
- package/dist/lib/store-badges.d.ts.map +1 -0
- package/dist/lib/store-badges.js +201 -0
- package/dist/lib/store-badges.js.map +1 -0
- package/dist/routes/redirect.d.ts +16 -3
- package/dist/routes/redirect.d.ts.map +1 -1
- package/dist/routes/redirect.js +26 -1
- package/dist/routes/redirect.js.map +1 -1
- package/dist/routes/redirect.launchpad.test.js +31 -0
- package/dist/routes/redirect.launchpad.test.js.map +1 -1
- package/dist/routes/redirect.store-url.test.d.ts +2 -0
- package/dist/routes/redirect.store-url.test.d.ts.map +1 -0
- package/dist/routes/redirect.store-url.test.js +144 -0
- package/dist/routes/redirect.store-url.test.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Store URLs leave the redirect exactly as configured.
|
|
3
|
+
*
|
|
4
|
+
* The destination decorator adds UTM tags, deep-link parameters and the
|
|
5
|
+
* opt-in click id — right for a website, wrong for a store listing. Google
|
|
6
|
+
* Play identifies the app by `?id=`, so a deep-link parameter named `id`
|
|
7
|
+
* replaced it and Play answered 404 to every Android visitor sent to install.
|
|
8
|
+
*
|
|
9
|
+
* The app-open guard cases matter as much as the fix: an in-app browser must
|
|
10
|
+
* still be sent to the web fallback (where a Universal Link / App Link gets a
|
|
11
|
+
* second chance to open an installed app), decorated as before.
|
|
12
|
+
*
|
|
13
|
+
* Follows redirect.launchpad.test.ts: the real `redirectRoutes` plugin through
|
|
14
|
+
* `fastify.inject()`, only the data layer mocked.
|
|
15
|
+
*/
|
|
16
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
17
|
+
import Fastify from 'fastify';
|
|
18
|
+
import { redirectRoutes, isAppStoreUrl } from './redirect.js';
|
|
19
|
+
const query = vi.fn();
|
|
20
|
+
vi.mock('../lib/database.js', () => ({
|
|
21
|
+
db: {
|
|
22
|
+
query: (...args) => query(...args),
|
|
23
|
+
},
|
|
24
|
+
}));
|
|
25
|
+
const DESKTOP_UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36';
|
|
26
|
+
const IPHONE_UA = 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1';
|
|
27
|
+
const ANDROID_CHROME_UA = 'Mozilla/5.0 (Linux; Android 14; Pixel 8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Mobile Safari/537.36';
|
|
28
|
+
const ANDROID_INSTAGRAM_UA = 'Mozilla/5.0 (Linux; Android 14; Pixel 8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Mobile Safari/537.36 Instagram 320.0.0.0';
|
|
29
|
+
const PLAY = 'https://play.google.com/store/apps/details?id=com.example.app';
|
|
30
|
+
const APP_STORE = 'https://apps.apple.com/app/id1234567890';
|
|
31
|
+
const WEB = 'https://example.com/event';
|
|
32
|
+
/** A link carrying everything the decorator would add, including an `id` parameter. */
|
|
33
|
+
function linkRow(overrides = {}) {
|
|
34
|
+
return {
|
|
35
|
+
id: '00000000-0000-0000-0000-0000000000aa',
|
|
36
|
+
short_code: 'abc123',
|
|
37
|
+
title: null,
|
|
38
|
+
description: null,
|
|
39
|
+
og_title: null,
|
|
40
|
+
og_description: null,
|
|
41
|
+
og_image_url: null,
|
|
42
|
+
original_url: null,
|
|
43
|
+
web_fallback_url: null,
|
|
44
|
+
ios_app_store_url: APP_STORE,
|
|
45
|
+
android_app_store_url: PLAY,
|
|
46
|
+
ios_universal_link: null,
|
|
47
|
+
android_app_link: null,
|
|
48
|
+
app_scheme: null,
|
|
49
|
+
deep_link_path: null,
|
|
50
|
+
launchpad_mode: 'inherit',
|
|
51
|
+
is_active: true,
|
|
52
|
+
warn_at: null,
|
|
53
|
+
owner_suspended_at: null,
|
|
54
|
+
expires_at: null,
|
|
55
|
+
targeting_rules: null,
|
|
56
|
+
template_settings: null,
|
|
57
|
+
org_settings: null,
|
|
58
|
+
utm_parameters: { source: 'newsletter', medium: 'email' },
|
|
59
|
+
deep_link_parameters: { id: 'evt_42', route: 'EVENT_VIEW' },
|
|
60
|
+
append_click_id: true,
|
|
61
|
+
...overrides,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function mockDb(row) {
|
|
65
|
+
query.mockReset();
|
|
66
|
+
query.mockImplementation(async (sql) => {
|
|
67
|
+
if (/information_schema\.columns/i.test(sql))
|
|
68
|
+
return { rows: [{ '?column?': 1 }], rowCount: 1 };
|
|
69
|
+
if (/^\s*SELECT\s+l\.\*/i.test(sql) || /FROM links l/i.test(sql))
|
|
70
|
+
return { rows: [row], rowCount: 1 };
|
|
71
|
+
return { rows: [], rowCount: 0 };
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
const get = (app, ua) => app.inject({ method: 'GET', url: '/abc123', headers: { 'user-agent': ua, host: 'go.example' } });
|
|
75
|
+
let app;
|
|
76
|
+
beforeEach(async () => {
|
|
77
|
+
app = Fastify();
|
|
78
|
+
await app.register(redirectRoutes);
|
|
79
|
+
await app.ready();
|
|
80
|
+
});
|
|
81
|
+
afterEach(async () => {
|
|
82
|
+
await new Promise((r) => setImmediate(r));
|
|
83
|
+
await app.close();
|
|
84
|
+
});
|
|
85
|
+
describe('store URLs are sent exactly as configured', () => {
|
|
86
|
+
it('Android Chrome → the Play URL untouched, so a deep-link `id` cannot replace the app id', async () => {
|
|
87
|
+
mockDb(linkRow({ web_fallback_url: WEB }));
|
|
88
|
+
const res = await get(app, ANDROID_CHROME_UA);
|
|
89
|
+
expect(res.statusCode).toBe(302);
|
|
90
|
+
expect(res.headers.location).toBe(PLAY);
|
|
91
|
+
});
|
|
92
|
+
it('iOS Safari → the App Store URL untouched', async () => {
|
|
93
|
+
mockDb(linkRow({ web_fallback_url: WEB }));
|
|
94
|
+
const res = await get(app, IPHONE_UA);
|
|
95
|
+
expect(res.statusCode).toBe(302);
|
|
96
|
+
expect(res.headers.location).toBe(APP_STORE);
|
|
97
|
+
});
|
|
98
|
+
it('a store URL configured as the web fallback is not decorated either', async () => {
|
|
99
|
+
mockDb(linkRow({ android_app_store_url: null, web_fallback_url: PLAY }));
|
|
100
|
+
const res = await get(app, ANDROID_CHROME_UA);
|
|
101
|
+
expect(res.headers.location).toBe(PLAY);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
describe('websites are decorated exactly as before', () => {
|
|
105
|
+
it('desktop → web fallback with UTM, deep-link parameters and the click id', async () => {
|
|
106
|
+
mockDb(linkRow({ web_fallback_url: WEB }));
|
|
107
|
+
const res = await get(app, DESKTOP_UA);
|
|
108
|
+
expect(res.statusCode).toBe(302);
|
|
109
|
+
const url = new URL(String(res.headers.location));
|
|
110
|
+
expect(url.origin + url.pathname).toBe(WEB);
|
|
111
|
+
expect(url.searchParams.get('id')).toBe('evt_42');
|
|
112
|
+
expect(url.searchParams.get('route')).toBe('EVENT_VIEW');
|
|
113
|
+
expect(url.searchParams.get('utm_source')).toBe('newsletter');
|
|
114
|
+
expect(url.searchParams.get('lf_click')).toBeTruthy();
|
|
115
|
+
});
|
|
116
|
+
it('app-open guard: an Android in-app browser still goes to the web fallback, not the store', async () => {
|
|
117
|
+
mockDb(linkRow({ web_fallback_url: WEB }));
|
|
118
|
+
const res = await get(app, ANDROID_INSTAGRAM_UA);
|
|
119
|
+
expect(res.statusCode).toBe(302);
|
|
120
|
+
const url = new URL(String(res.headers.location));
|
|
121
|
+
expect(url.hostname).toBe('example.com');
|
|
122
|
+
expect(url.searchParams.get('id')).toBe('evt_42');
|
|
123
|
+
});
|
|
124
|
+
it('app-open guard: an App Link still wins over every fallback', async () => {
|
|
125
|
+
mockDb(linkRow({ web_fallback_url: WEB, android_app_link: 'https://app.example.com/e/42' }));
|
|
126
|
+
const res = await get(app, ANDROID_CHROME_UA);
|
|
127
|
+
expect(new URL(String(res.headers.location)).hostname).toBe('app.example.com');
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
describe('isAppStoreUrl', () => {
|
|
131
|
+
it('recognises Google Play and the App Store, whatever the case', () => {
|
|
132
|
+
expect(isAppStoreUrl(PLAY)).toBe(true);
|
|
133
|
+
expect(isAppStoreUrl(APP_STORE)).toBe(true);
|
|
134
|
+
expect(isAppStoreUrl('https://itunes.apple.com/us/app/id1')).toBe(true);
|
|
135
|
+
expect(isAppStoreUrl('https://PLAY.google.com/store/apps/details?id=x')).toBe(true);
|
|
136
|
+
});
|
|
137
|
+
it('does not treat websites, look-alikes or garbage as stores', () => {
|
|
138
|
+
expect(isAppStoreUrl(WEB)).toBe(false);
|
|
139
|
+
expect(isAppStoreUrl('https://play.google.com.evil.example/x')).toBe(false);
|
|
140
|
+
expect(isAppStoreUrl('https://example.com/?u=https://play.google.com')).toBe(false);
|
|
141
|
+
expect(isAppStoreUrl('not a url')).toBe(false);
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
//# sourceMappingURL=redirect.store-url.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redirect.store-url.test.js","sourceRoot":"","sources":["../../src/routes/redirect.store-url.test.ts"],"names":[],"mappings":"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"}
|