🏷️ Search for custom tailnet name offers with keywords.
1import { log } from '$helpers/logging';
2import { getTailnetNames, validateTailnetNames } from '$helpers/tailnet';
3import { extractMatchingTokens, storeTokens } from '$helpers/tokens';
4import type { Offer } from '$types/tokens';
5
6export const getTailcontrolCookie = async (): Promise<{
7 value: string;
8} | null> => {
9 return await browser.cookies.get({
10 url: 'https://login.tailscale.com',
11 name: 'tailcontrol',
12 });
13};
14
15export const fetchTailscaleOffers = async (cookieValue: string) => {
16 return await fetch('https://login.tailscale.com/admin/api/tcd/offers', {
17 headers: {
18 accept: 'application/json, text/plain, */*',
19 'accept-language': 'en-US,en;q=0.9',
20 'cache-control': 'no-cache',
21 pragma: 'no-cache',
22 cookie: `tailcontrol=${cookieValue}; tailwww-ui=true`,
23 },
24 referrer: 'https://login.tailscale.com/admin/dns',
25 referrerPolicy: 'strict-origin-when-cross-origin',
26 method: 'GET',
27 mode: 'cors',
28 });
29};
30
31const handleCheckOffersError = (errMsg: string) => {
32 log(errMsg, 'ERROR', 'Offers');
33 log(
34 'Checking offers process finished due to an error. See line above.',
35 'ERROR',
36 'Offers',
37 );
38
39 browser.alarms.clear('checkOffersAlarm');
40};
41
42export const checkTailscaleOffers = async () => {
43 log(
44 'Checking offers process started. Searching Tailscale for tailnet name offers...',
45 'INFO',
46 'Offers',
47 );
48 let tailnetNames: string[] = [];
49
50 try {
51 tailnetNames = await getTailnetNames();
52 } catch {
53 log(
54 'Failed to get tailnetNames from storage. Defaulting to empty array.',
55 'WARNING',
56 'Offers',
57 );
58 }
59
60 if (!validateTailnetNames(tailnetNames)) {
61 handleCheckOffersError(
62 'No tailnet keywords set. User needs to add at least one tailnet keyword.',
63 );
64 return;
65 }
66
67 try {
68 const cookie = await getTailcontrolCookie();
69 if (!cookie) {
70 handleCheckOffersError(
71 'Tailcontrol cookie not found. User needs to log in to Tailscale.',
72 );
73 return;
74 }
75
76 const cookieValue = cookie.value;
77 const response = await fetchTailscaleOffers(cookieValue);
78 const body: { data?: Offer } = await response.json();
79
80 if (body.data?.tcds) {
81 const tokens = extractMatchingTokens(body.data.tcds, tailnetNames);
82
83 if (tokens.length > 0) {
84 log(
85 `Found matching tokens: ${JSON.stringify(tokens)}`,
86 'INFO',
87 'Offers',
88 );
89
90 storeTokens(tokens);
91 } else {
92 log(
93 "No matching tailnet name offers found that match the user's defined keywords.",
94 'INFO',
95 'Offers',
96 );
97 }
98 }
99 } catch (e) {
100 const errorMsg = e instanceof Error ? e.message : 'Unknown error';
101 handleCheckOffersError(errorMsg || 'Unknown error');
102 }
103};