tokenwait 0.1.1 → 0.1.3
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/bin/status.js +2 -2
- package/bin/tokenwait.js +2 -1
- package/lib/api.js +31 -3
- package/lib/rotation.js +11 -6
- package/lib/util.js +20 -1
- package/lib/worker.js +5 -2
- package/package.json +1 -1
package/bin/status.js
CHANGED
|
@@ -29,13 +29,13 @@ function main() {
|
|
|
29
29
|
orig = (r.stdout || '').replace(/\s+$/, '');
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
const { p, readJson,
|
|
32
|
+
const { p, readJson, adLineLinked } = require('../lib/util');
|
|
33
33
|
const { pick } = require('../lib/rotation');
|
|
34
34
|
const worker = require('../lib/worker');
|
|
35
35
|
const ads = readJson(p('ads.json'), null);
|
|
36
36
|
const cfg = readJson(p('config.json'), {});
|
|
37
37
|
const ad = ads && pick(ads.ads, Date.now(), cfg.rotate_seconds || 30);
|
|
38
|
-
const line =
|
|
38
|
+
const line = adLineLinked(ad, ads && ads.label);
|
|
39
39
|
if (worker.adsStale()) worker.spawnWorker();
|
|
40
40
|
|
|
41
41
|
let out;
|
package/bin/tokenwait.js
CHANGED
|
@@ -81,7 +81,8 @@ What it changes:
|
|
|
81
81
|
• stores ads, counters and an install key in ~/.tokenwait
|
|
82
82
|
What it never does:
|
|
83
83
|
• read your code, prompts, responses or transcripts
|
|
84
|
-
• send anything except ad ids, impression counts, waiting time
|
|
84
|
+
• send anything except ad ids, impression counts, waiting time, the install key,
|
|
85
|
+
the package version and which agents and IDE add-ons are connected
|
|
85
86
|
Undo at any time: npx tokenwait uninstall
|
|
86
87
|
`;
|
|
87
88
|
|
package/lib/api.js
CHANGED
|
@@ -24,8 +24,34 @@ function credentials() {
|
|
|
24
24
|
return c && c.server === server() && c.client_id && c.client_secret ? c : null;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
/**
|
|
28
|
+
* Что стоит на этой установке — для статистики оператора (без данных о человеке):
|
|
29
|
+
* подключённые агенты и дополнения IDE, которые выходили на связь за последние 7 дней (~/.tokenwait/ide.json).
|
|
30
|
+
*/
|
|
31
|
+
function installInfo() {
|
|
32
|
+
const out = {};
|
|
33
|
+
try {
|
|
34
|
+
const agents = require('./agents');
|
|
35
|
+
out['x-tokenwait-agents'] = Object.keys(agents.AGENTS).filter((id) => agents.isInstalled(id)).join(',');
|
|
36
|
+
} catch (_) {}
|
|
37
|
+
const ide = readJson(p('ide.json'), {});
|
|
38
|
+
const fresh = Object.entries(ide && typeof ide === 'object' ? ide : {})
|
|
39
|
+
.filter(([k, v]) => /^(jetbrains|vscode)$/.test(k) && v && Date.now() - Number(v.t) < 7 * 86400_000 && /^\d+\.\d+\.\d+$/.test(String(v.v)))
|
|
40
|
+
.map(([k, v]) => k + '/' + v.v);
|
|
41
|
+
out['x-tokenwait-ide'] = fresh.join(',');
|
|
42
|
+
return out;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Отметка дополнения IDE: вызывают JetBrains и VS Code при запуске и раз в час. */
|
|
46
|
+
function markIde(kind, version) {
|
|
47
|
+
if (!/^(jetbrains|vscode)$/.test(kind)) return;
|
|
48
|
+
const ide = readJson(p('ide.json'), {});
|
|
49
|
+
ide[kind] = { v: String(version), t: Date.now() };
|
|
50
|
+
writeJson(p('ide.json'), ide);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function request(method, path, body, auth = true, timeoutMs = 5000, extraHeaders = {}) {
|
|
54
|
+
const headers = { accept: 'application/json', 'user-agent': 'tokenwait/' + require('../package.json').version, ...extraHeaders };
|
|
29
55
|
if (body !== undefined) headers['content-type'] = 'application/json';
|
|
30
56
|
if (auth) {
|
|
31
57
|
const c = credentials() || (await register());
|
|
@@ -55,7 +81,7 @@ async function register() {
|
|
|
55
81
|
}
|
|
56
82
|
|
|
57
83
|
async function fetchAds() {
|
|
58
|
-
const r = await request('GET', '/v1/ads');
|
|
84
|
+
const r = await request('GET', '/v1/ads', undefined, true, 5000, installInfo());
|
|
59
85
|
const data = {
|
|
60
86
|
fetched_at: Date.now(),
|
|
61
87
|
ttl_seconds: Math.min(Math.max(Number(r.ttl_seconds) || 600, 60), 3600),
|
|
@@ -76,6 +102,8 @@ async function me() {
|
|
|
76
102
|
}
|
|
77
103
|
|
|
78
104
|
module.exports = {
|
|
105
|
+
installInfo,
|
|
106
|
+
markIde,
|
|
79
107
|
HttpError,
|
|
80
108
|
server,
|
|
81
109
|
config,
|
package/lib/rotation.js
CHANGED
|
@@ -33,12 +33,17 @@ function pick(ads, t, rotateSeconds) {
|
|
|
33
33
|
function normalizeAds(list) {
|
|
34
34
|
if (!Array.isArray(list)) return [];
|
|
35
35
|
return list
|
|
36
|
-
.map((a) =>
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
36
|
+
.map((a) => {
|
|
37
|
+
const ad = {
|
|
38
|
+
id: sanitizeText(a && a.id, 64),
|
|
39
|
+
text: sanitizeText(a && a.text),
|
|
40
|
+
url: safeUrl(a && a.url),
|
|
41
|
+
weight: Math.max(1, Math.floor(Number(a && a.weight)) || 1),
|
|
42
|
+
};
|
|
43
|
+
// host — подпись в скобках с сервера (ссылка ведёт на наш /r/, поэтому домен из неё не показываем)
|
|
44
|
+
if (a && typeof a.host === 'string') ad.host = sanitizeText(a.host, 40);
|
|
45
|
+
return ad;
|
|
46
|
+
})
|
|
42
47
|
.filter((a) => a.id && a.text)
|
|
43
48
|
.slice(0, 50);
|
|
44
49
|
}
|
package/lib/util.js
CHANGED
|
@@ -13,13 +13,30 @@ function adLabel(label) {
|
|
|
13
13
|
return /\b(ad|ads|sponsored|advert\w*|promo\w*)\b|реклам/i.test(s) ? s : s + ' · Ad';
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
/** Что показать в скобках: host с сервера (имя рекламодателя или сайт), иначе домен ссылки. */
|
|
17
|
+
function adHost(ad) {
|
|
18
|
+
return typeof ad.host === 'string' ? sanitizeText(ad.host, 40) : hostOf(ad.url);
|
|
19
|
+
}
|
|
20
|
+
|
|
16
21
|
/** Строка показа: «TokenWait · Ad · текст (сайт)». */
|
|
17
22
|
function adLine(ad, label) {
|
|
18
23
|
if (!ad || !ad.text) return '';
|
|
19
|
-
const host =
|
|
24
|
+
const host = adHost(ad);
|
|
20
25
|
return adLabel(label) + ' · ' + ad.text + (host ? ' (' + host + ')' : '');
|
|
21
26
|
}
|
|
22
27
|
|
|
28
|
+
/**
|
|
29
|
+
* То же, но текст объявления — кликабельная ссылка OSC 8 (Cmd/Ctrl+клик в iTerm2, WezTerm, Kitty, терминалах IDE).
|
|
30
|
+
* Терминалы без поддержки показывают обычный текст. Отключается TOKENWAIT_NO_LINKS=1.
|
|
31
|
+
*/
|
|
32
|
+
function adLineLinked(ad, label) {
|
|
33
|
+
const plain = adLine(ad, label);
|
|
34
|
+
const url = ad && safeUrl(ad.url);
|
|
35
|
+
if (!plain || !url || process.env.TOKENWAIT_NO_LINKS === '1' || /[\u0000-\u0020\u007f-\u009f]/.test(url)) return plain;
|
|
36
|
+
const head = adLabel(label) + ' · ';
|
|
37
|
+
return head + '\x1b]8;;' + url + '\x07' + plain.slice(head.length) + '\x1b]8;;\x07';
|
|
38
|
+
}
|
|
39
|
+
|
|
23
40
|
function home() {
|
|
24
41
|
return process.env.TOKENWAIT_HOME || path.join(os.homedir(), '.tokenwait');
|
|
25
42
|
}
|
|
@@ -185,6 +202,8 @@ module.exports = {
|
|
|
185
202
|
DEFAULT_LABEL,
|
|
186
203
|
adLabel,
|
|
187
204
|
adLine,
|
|
205
|
+
adLineLinked,
|
|
206
|
+
adHost,
|
|
188
207
|
home,
|
|
189
208
|
p,
|
|
190
209
|
readJson,
|
package/lib/worker.js
CHANGED
|
@@ -54,8 +54,11 @@ async function report() {
|
|
|
54
54
|
if (Date.now() - (pend.last_report || 0) < (ads.report_seconds || 60) * 1000) return null;
|
|
55
55
|
const { counts, rest } = toImpressions(pend.ads, impSec);
|
|
56
56
|
if (!Object.keys(counts).length) return null;
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
// Остаток неполных показов переносим вместе с его временем ожидания: иначе в следующем отчёте
|
|
58
|
+
// показ, собранный из остатка, сервер отклонит (показов не больше, чем время ожидания).
|
|
59
|
+
const restMs = Object.values(rest).reduce((x, y) => x + y, 0);
|
|
60
|
+
const waitMs = Math.max(0, (pend.wait_ms || 0) - restMs);
|
|
61
|
+
writeJson(p('pending.json'), { ads: rest, wait_ms: Math.min(restMs, pend.wait_ms || 0), last_report: Date.now() });
|
|
59
62
|
return { counts, waitMs };
|
|
60
63
|
});
|
|
61
64
|
if (!taken) return null;
|
package/package.json
CHANGED