clixad 0.0.1-beta.45 → 0.0.1-beta.47
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.
|
@@ -13,6 +13,10 @@ function isAdFormat(value) {
|
|
|
13
13
|
// src/sponsor/direct.ts
|
|
14
14
|
var DIRECT_ASK_TIMEOUT_MS = 1500;
|
|
15
15
|
var MAX_TEXT = 200;
|
|
16
|
+
function parseMeasure(body) {
|
|
17
|
+
const raw = body?.measure;
|
|
18
|
+
return typeof raw === "string" && raw.length > 0 && raw.length <= 2e3 ? raw : void 0;
|
|
19
|
+
}
|
|
16
20
|
function sanitizeAdText(raw) {
|
|
17
21
|
return raw.replace(/\u001b\[[0-?]*[ -/]*[@-~]/g, "").replace(/\u001b\][^\u0007\u001b]*(?:\u0007|\u001b\\)?/g, "").replace(/[\u0000-\u001f\u007f-\u009f]/g, " ").replace(/[--]/g, "").replace(/[-]/g, "").replace(/\s+/g, " ").trim();
|
|
18
22
|
}
|
|
@@ -30,22 +34,54 @@ function parseDirectLines(body) {
|
|
|
30
34
|
seen.add(e.format);
|
|
31
35
|
continue;
|
|
32
36
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
let clickUrl;
|
|
37
|
-
try {
|
|
38
|
-
const url = new URL(e.click_url);
|
|
39
|
-
if (url.protocol !== "https:" && url.protocol !== "http:") continue;
|
|
40
|
-
clickUrl = url.toString();
|
|
41
|
-
} catch {
|
|
42
|
-
continue;
|
|
43
|
-
}
|
|
44
|
-
out.push({ format: e.format, text, serveToken: e.serve_token, clickUrl });
|
|
37
|
+
const line = readServedLine(e);
|
|
38
|
+
if (!line) continue;
|
|
39
|
+
out.push(line);
|
|
45
40
|
seen.add(e.format);
|
|
46
41
|
}
|
|
47
42
|
return out;
|
|
48
43
|
}
|
|
44
|
+
function parseFallbackLines(body) {
|
|
45
|
+
const raw = body?.fallbacks;
|
|
46
|
+
if (!Array.isArray(raw)) return [];
|
|
47
|
+
const out = [];
|
|
48
|
+
for (const item of raw) {
|
|
49
|
+
if (!item || typeof item !== "object") continue;
|
|
50
|
+
const line = readServedLine(item);
|
|
51
|
+
if (!line || out.some((o) => o.format === line.format)) continue;
|
|
52
|
+
out.push(line);
|
|
53
|
+
}
|
|
54
|
+
return out;
|
|
55
|
+
}
|
|
56
|
+
function readServedLine(e) {
|
|
57
|
+
if (!isAdFormat(e.format)) return null;
|
|
58
|
+
if (typeof e.text !== "string" || typeof e.serve_token !== "string" || typeof e.click_url !== "string") return null;
|
|
59
|
+
const text = sanitizeAdText(e.text).slice(0, MAX_TEXT);
|
|
60
|
+
if (!text || !e.serve_token || e.serve_token.length > 2e3) return null;
|
|
61
|
+
let clickUrl;
|
|
62
|
+
try {
|
|
63
|
+
const url = new URL(e.click_url);
|
|
64
|
+
if (url.protocol !== "https:" && url.protocol !== "http:") return null;
|
|
65
|
+
clickUrl = url.toString();
|
|
66
|
+
} catch {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
const shortUrl = typeof e.short_url === "string" && shortLinkText(e.short_url) ? e.short_url : void 0;
|
|
70
|
+
return { format: e.format, text, serveToken: e.serve_token, clickUrl, ...shortUrl ? { shortUrl } : {} };
|
|
71
|
+
}
|
|
72
|
+
var MAX_LINK_TEXT = 40;
|
|
73
|
+
function shortLinkText(clickUrl) {
|
|
74
|
+
let url;
|
|
75
|
+
try {
|
|
76
|
+
url = new URL(clickUrl);
|
|
77
|
+
} catch {
|
|
78
|
+
return void 0;
|
|
79
|
+
}
|
|
80
|
+
if (url.protocol !== "https:" || url.search || url.hash || url.username || url.password) return void 0;
|
|
81
|
+
const text = `${url.host}${url.pathname === "/" ? "" : url.pathname}`;
|
|
82
|
+
if (text.length > MAX_LINK_TEXT || !/^[\x21-\x7e]+$/.test(text)) return void 0;
|
|
83
|
+
return text;
|
|
84
|
+
}
|
|
49
85
|
|
|
50
86
|
// src/edge.ts
|
|
51
87
|
function detectEdgeBlock(status, headers, body) {
|
|
@@ -1107,7 +1143,15 @@ var GatewayClient = class {
|
|
|
1107
1143
|
});
|
|
1108
1144
|
if (res.status === 404) return { supported: false, lines: [] };
|
|
1109
1145
|
if (!res.ok) return { supported: true, lines: [], failed: true };
|
|
1110
|
-
|
|
1146
|
+
const body = await res.json();
|
|
1147
|
+
const fallbacks = parseFallbackLines(body);
|
|
1148
|
+
const measure = parseMeasure(body);
|
|
1149
|
+
return {
|
|
1150
|
+
supported: true,
|
|
1151
|
+
lines: parseDirectLines(body),
|
|
1152
|
+
...fallbacks.length > 0 ? { fallbacks } : {},
|
|
1153
|
+
...measure ? { measure } : {}
|
|
1154
|
+
};
|
|
1111
1155
|
} catch {
|
|
1112
1156
|
return { supported: true, lines: [], failed: true };
|
|
1113
1157
|
}
|
|
@@ -1124,7 +1168,14 @@ var GatewayClient = class {
|
|
|
1124
1168
|
method: "POST",
|
|
1125
1169
|
headers: this.headers(),
|
|
1126
1170
|
...signal ? { signal } : {},
|
|
1127
|
-
body: JSON.stringify({
|
|
1171
|
+
body: JSON.stringify({
|
|
1172
|
+
impressions: impressions.map((i) => ({
|
|
1173
|
+
format: i.format,
|
|
1174
|
+
serve_token: i.serveToken,
|
|
1175
|
+
...i.visibleMs !== void 0 ? { visible_ms: Math.round(i.visibleMs) } : {},
|
|
1176
|
+
...i.link ? { link: i.link } : {}
|
|
1177
|
+
}))
|
|
1178
|
+
})
|
|
1128
1179
|
});
|
|
1129
1180
|
if (!res.ok) return 0;
|
|
1130
1181
|
const body = await res.json();
|
|
@@ -1133,6 +1184,48 @@ var GatewayClient = class {
|
|
|
1133
1184
|
return 0;
|
|
1134
1185
|
}
|
|
1135
1186
|
}
|
|
1187
|
+
/**
|
|
1188
|
+
* Report how long each place a turn asked about stayed on screen with a
|
|
1189
|
+
* line in it, with the turn's measure token: what a booked week is
|
|
1190
|
+
* forecast on. Never throws; answers how many places counted.
|
|
1191
|
+
*/
|
|
1192
|
+
async reportSponsorViews(measure, places, signal) {
|
|
1193
|
+
if (places.length === 0) return 0;
|
|
1194
|
+
try {
|
|
1195
|
+
const res = await gatewayFetch(`${this.config.gatewayUrl}/v1/sponsor/direct/views`, {
|
|
1196
|
+
method: "POST",
|
|
1197
|
+
headers: this.headers(),
|
|
1198
|
+
...signal ? { signal } : {},
|
|
1199
|
+
body: JSON.stringify({ measure, places: places.map((p) => ({ format: p.format, visible_ms: Math.round(p.visibleMs) })) })
|
|
1200
|
+
});
|
|
1201
|
+
if (!res.ok) return 0;
|
|
1202
|
+
const body = await res.json();
|
|
1203
|
+
return typeof body.counted === "number" ? body.counted : 0;
|
|
1204
|
+
} catch {
|
|
1205
|
+
return 0;
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
/**
|
|
1209
|
+
* Report the fallback lines a turn drew, in one call, after the turn is
|
|
1210
|
+
* over. They are counted and earn nothing, because nobody pays for them being
|
|
1211
|
+
* shown, so this answers how many were counted. Never throws.
|
|
1212
|
+
*/
|
|
1213
|
+
async reportAffiliateImpressions(impressions, signal) {
|
|
1214
|
+
if (impressions.length === 0) return 0;
|
|
1215
|
+
try {
|
|
1216
|
+
const res = await gatewayFetch(`${this.config.gatewayUrl}/v1/sponsor/affiliate/impression`, {
|
|
1217
|
+
method: "POST",
|
|
1218
|
+
headers: this.headers(),
|
|
1219
|
+
...signal ? { signal } : {},
|
|
1220
|
+
body: JSON.stringify({ impressions: impressions.map((i) => ({ format: i.format, serve_token: i.serveToken })) })
|
|
1221
|
+
});
|
|
1222
|
+
if (!res.ok) return 0;
|
|
1223
|
+
const body = await res.json();
|
|
1224
|
+
return typeof body.counted === "number" ? body.counted : 0;
|
|
1225
|
+
} catch {
|
|
1226
|
+
return 0;
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1136
1229
|
/**
|
|
1137
1230
|
* Search the web, through the gateway.
|
|
1138
1231
|
*
|
|
@@ -6573,6 +6666,7 @@ export {
|
|
|
6573
6666
|
AD_FORMATS,
|
|
6574
6667
|
EXTRA_AD_FORMATS,
|
|
6575
6668
|
DIRECT_ASK_TIMEOUT_MS,
|
|
6669
|
+
shortLinkText,
|
|
6576
6670
|
accessColumn,
|
|
6577
6671
|
premiumLocked,
|
|
6578
6672
|
premiumUnlockHint,
|
package/dist/clixad.mjs
CHANGED
|
@@ -54,7 +54,7 @@ import {
|
|
|
54
54
|
takeConfigNotices,
|
|
55
55
|
toolSchemaFor,
|
|
56
56
|
usd
|
|
57
|
-
} from "./chunk-
|
|
57
|
+
} from "./chunk-TKGMAY4C.mjs";
|
|
58
58
|
|
|
59
59
|
// src/tui/load.ts
|
|
60
60
|
import * as nodeModule from "node:module";
|
|
@@ -133,7 +133,7 @@ async function withProductionReact(load) {
|
|
|
133
133
|
}
|
|
134
134
|
function loadTui() {
|
|
135
135
|
boundInkCaches();
|
|
136
|
-
return withProductionReact(() => import("./tui-
|
|
136
|
+
return withProductionReact(() => import("./tui-US36BPP3.mjs"));
|
|
137
137
|
}
|
|
138
138
|
|
|
139
139
|
// src/title.ts
|
|
@@ -89,6 +89,7 @@ import {
|
|
|
89
89
|
savedEffort,
|
|
90
90
|
shells,
|
|
91
91
|
shortCredits,
|
|
92
|
+
shortLinkText,
|
|
92
93
|
shortfallHeadline,
|
|
93
94
|
shouldCompact,
|
|
94
95
|
shouldSuggestInit,
|
|
@@ -104,7 +105,7 @@ import {
|
|
|
104
105
|
visibleLength,
|
|
105
106
|
webSearchCostLine,
|
|
106
107
|
welcomeBanner
|
|
107
|
-
} from "./chunk-
|
|
108
|
+
} from "./chunk-TKGMAY4C.mjs";
|
|
108
109
|
|
|
109
110
|
// src/tui/index.tsx
|
|
110
111
|
import "react";
|
|
@@ -276,22 +277,35 @@ var SIDEBAR_MIN_COLS = 50 + SIDEBAR_CARD_COLS;
|
|
|
276
277
|
function adFitsFormat(format, cols) {
|
|
277
278
|
return format === "sidebar_card" ? cols >= SIDEBAR_MIN_COLS : adFitsWidth(cols);
|
|
278
279
|
}
|
|
279
|
-
|
|
280
|
+
var LINK_SEPARATOR = " \xB7 ";
|
|
281
|
+
function sidebarCard(line2, cols, linkText) {
|
|
280
282
|
if (!adFitsFormat("sidebar_card", cols)) return null;
|
|
281
283
|
const text = line2.trim();
|
|
282
284
|
if (!text) return null;
|
|
283
285
|
const body = visibleLength(text) <= SIDEBAR_BODY_COLS ? text : `${sliceColumns(text, SIDEBAR_BODY_COLS - 1).trimEnd()}\u2026`;
|
|
284
|
-
|
|
286
|
+
const label = `${SPONSOR_SIGIL} ${AD_LABEL}`;
|
|
287
|
+
const link = linkText && visibleLength(label) + visibleLength(LINK_SEPARATOR) + visibleLength(linkText) <= SIDEBAR_BODY_COLS ? linkText : null;
|
|
288
|
+
return { rule: SIDEBAR_RULE, label, link, body };
|
|
285
289
|
}
|
|
286
|
-
function sponsorRow(line2, cols, origin) {
|
|
290
|
+
function sponsorRow(line2, cols, origin, linkText, linkRequired = false) {
|
|
287
291
|
const marker = origin === "house" || cols >= AD_FULL_PREFIX_MIN_COLS ? HOUSE_MARKER_ROW : AD_MARKER_ROW;
|
|
288
292
|
const label = origin === "network" ? LABEL_ROW : null;
|
|
289
293
|
if (origin === "network" && !adFitsWidth(cols)) return null;
|
|
290
294
|
const room = cols - visibleLength(marker) - (label ? visibleLength(label) : 0);
|
|
291
295
|
if (room < MIN_BODY_COLS) return null;
|
|
296
|
+
if (origin === "network" && linkText) {
|
|
297
|
+
const linkCols = visibleLength(LINK_SEPARATOR) + visibleLength(linkText);
|
|
298
|
+
const whole = visibleLength(line2) + linkCols;
|
|
299
|
+
if (whole <= room) return { marker, label, body: line2, link: linkText, origin };
|
|
300
|
+
if (linkRequired && room - linkCols >= MIN_LINKED_COPY_COLS) {
|
|
301
|
+
const copy = room - linkCols;
|
|
302
|
+
return { marker, label, body: `${sliceColumns(line2, copy - 1).trimEnd()}\u2026`, link: linkText, origin };
|
|
303
|
+
}
|
|
304
|
+
}
|
|
292
305
|
const body = visibleLength(line2) <= room ? line2 : `${sliceColumns(line2, room - 1).trimEnd()}\u2026`;
|
|
293
|
-
return { marker, label, body, origin };
|
|
306
|
+
return { marker, label, body, link: null, origin };
|
|
294
307
|
}
|
|
308
|
+
var MIN_LINKED_COPY_COLS = 12;
|
|
295
309
|
function adColorEnabled({ isTTY, env }) {
|
|
296
310
|
if (!colorEnabled({ isTTY, env })) return false;
|
|
297
311
|
if (env.CI !== void 0 && env.CI !== "") return false;
|
|
@@ -348,6 +362,46 @@ function createSponsorSlot(config, format = "top_bar") {
|
|
|
348
362
|
};
|
|
349
363
|
}
|
|
350
364
|
|
|
365
|
+
// src/sponsor/visibility.ts
|
|
366
|
+
var VisibilityClock = class {
|
|
367
|
+
tracks = /* @__PURE__ */ new Map();
|
|
368
|
+
/** The keys on screen as of `now`. Every key not in `onScreen` is off screen from `now`. */
|
|
369
|
+
observe(onScreen, now) {
|
|
370
|
+
const visible = new Set(onScreen);
|
|
371
|
+
for (const [key, track] of this.tracks) {
|
|
372
|
+
if (track.since !== null && !visible.has(key)) {
|
|
373
|
+
track.done.push([track.since, now]);
|
|
374
|
+
track.since = null;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
for (const key of visible) {
|
|
378
|
+
const track = this.tracks.get(key) ?? { since: null, done: [] };
|
|
379
|
+
if (track.since === null) track.since = now;
|
|
380
|
+
this.tracks.set(key, track);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* The longest unbroken stretch `key` was on screen up to `now`, counting
|
|
385
|
+
* only time from `from` on (a place is measured from the moment a booked
|
|
386
|
+
* line could first have been drawn in it). Zero for a key never seen.
|
|
387
|
+
*/
|
|
388
|
+
longest(key, now, from = Number.NEGATIVE_INFINITY) {
|
|
389
|
+
const track = this.tracks.get(key);
|
|
390
|
+
if (!track) return 0;
|
|
391
|
+
const stretches = track.since === null ? track.done : [...track.done, [track.since, now]];
|
|
392
|
+
let best = 0;
|
|
393
|
+
for (const [start, end] of stretches) best = Math.max(best, Math.min(end, now) - Math.max(start, from));
|
|
394
|
+
return Math.max(0, best);
|
|
395
|
+
}
|
|
396
|
+
/** Forget everything: a new turn measures from nothing. */
|
|
397
|
+
reset() {
|
|
398
|
+
this.tracks.clear();
|
|
399
|
+
}
|
|
400
|
+
};
|
|
401
|
+
function placeKey(format) {
|
|
402
|
+
return `place:${format}`;
|
|
403
|
+
}
|
|
404
|
+
|
|
351
405
|
// src/sponsor/hyperlink.ts
|
|
352
406
|
function version(v) {
|
|
353
407
|
const m = /^(\d+)(?:\.(\d+))?/.exec(v ?? "");
|
|
@@ -1880,12 +1934,8 @@ function App({
|
|
|
1880
1934
|
const [startedAt, setStartedAt] = useState(0);
|
|
1881
1935
|
const [quitHint, setQuitHint] = useState(false);
|
|
1882
1936
|
const [busyLabel, setBusyLabel] = useState(WORKING);
|
|
1883
|
-
const [sponsor, setSponsor] = useState(
|
|
1884
|
-
|
|
1885
|
-
);
|
|
1886
|
-
const [adSlots, setAdSlots] = useState(
|
|
1887
|
-
{}
|
|
1888
|
-
);
|
|
1937
|
+
const [sponsor, setSponsor] = useState(null);
|
|
1938
|
+
const [adSlots, setAdSlots] = useState({});
|
|
1889
1939
|
const messagesRef = useRef(messages);
|
|
1890
1940
|
messagesRef.current = messages;
|
|
1891
1941
|
const busyRef = useRef(false);
|
|
@@ -2010,9 +2060,13 @@ function App({
|
|
|
2010
2060
|
const sponsorCallRef = useRef(0);
|
|
2011
2061
|
const pendingImpressionRef = useRef(/* @__PURE__ */ new Map());
|
|
2012
2062
|
const pendingDirectRef = useRef(/* @__PURE__ */ new Map());
|
|
2063
|
+
const pendingFallbackRef = useRef(/* @__PURE__ */ new Map());
|
|
2013
2064
|
const directSupportedRef = useRef(true);
|
|
2014
2065
|
const knownBookedRef = useRef(/* @__PURE__ */ new Set());
|
|
2015
2066
|
const drawnRef = useRef(/* @__PURE__ */ new Set());
|
|
2067
|
+
const visibilityRef = useRef(new VisibilityClock());
|
|
2068
|
+
const linkModeRef = useRef(/* @__PURE__ */ new Map());
|
|
2069
|
+
const measureRef = useRef(null);
|
|
2016
2070
|
const sponsorSlotRef = useRef(
|
|
2017
2071
|
/* @__PURE__ */ new Map()
|
|
2018
2072
|
);
|
|
@@ -2255,6 +2309,9 @@ function App({
|
|
|
2255
2309
|
const nextSponsor = useCallback(
|
|
2256
2310
|
(modelId) => {
|
|
2257
2311
|
const call = ++sponsorCallRef.current;
|
|
2312
|
+
visibilityRef.current.reset();
|
|
2313
|
+
linkModeRef.current = /* @__PURE__ */ new Map();
|
|
2314
|
+
measureRef.current = null;
|
|
2258
2315
|
if (!sponsorEnabled({
|
|
2259
2316
|
isTTY: stdout.isTTY,
|
|
2260
2317
|
env: process.env,
|
|
@@ -2277,9 +2334,13 @@ function App({
|
|
|
2277
2334
|
} else {
|
|
2278
2335
|
setSponsor(null);
|
|
2279
2336
|
}
|
|
2280
|
-
const fetchNetwork = (format) => {
|
|
2337
|
+
const fetchNetwork = (format, fallback) => {
|
|
2281
2338
|
void remoteSponsorRef.current[format].fetch().then((line2) => {
|
|
2282
|
-
if (
|
|
2339
|
+
if (call !== sponsorCallRef.current) return;
|
|
2340
|
+
if (!line2) {
|
|
2341
|
+
if (fallback) paintFallback(fallback);
|
|
2342
|
+
return;
|
|
2343
|
+
}
|
|
2283
2344
|
if (!adFitsFormat(format, colsRef.current)) return;
|
|
2284
2345
|
if (format !== "top_bar" && modalOpenRef.current) return;
|
|
2285
2346
|
if (format === "top_bar") {
|
|
@@ -2303,17 +2364,38 @@ function App({
|
|
|
2303
2364
|
if (format !== "top_bar" && modalOpenRef.current) return;
|
|
2304
2365
|
const drawnId = `direct:${call}:${format}`;
|
|
2305
2366
|
const link = line2.clickUrl;
|
|
2367
|
+
const linkable2 = hyperlinksEnabled({ isTTY: stdout.isTTY, env: process.env });
|
|
2368
|
+
const linkText = !linkable2 && line2.shortUrl ? shortLinkText(line2.shortUrl) : void 0;
|
|
2369
|
+
const extra = linkText ? { link, linkText, linkRequired: true } : { link };
|
|
2306
2370
|
if (format === "top_bar") {
|
|
2307
|
-
if (!sponsorRow(line2.text, colsRef.current, "network")) return;
|
|
2308
|
-
setSponsor({ text: line2.text, origin: "network", impressionId: drawnId,
|
|
2371
|
+
if (!sponsorRow(line2.text, colsRef.current, "network", linkText, true)) return;
|
|
2372
|
+
setSponsor({ text: line2.text, origin: "network", impressionId: drawnId, ...extra });
|
|
2309
2373
|
} else {
|
|
2310
|
-
setAdSlots((slots) => ({ ...slots, [format]: { text: line2.text, impressionId: drawnId,
|
|
2374
|
+
setAdSlots((slots) => ({ ...slots, [format]: { text: line2.text, impressionId: drawnId, ...extra } }));
|
|
2311
2375
|
}
|
|
2312
2376
|
pendingDirectRef.current.set(format, { call, serveToken: line2.serveToken, drawnId });
|
|
2313
2377
|
};
|
|
2378
|
+
const paintFallback = (line2) => {
|
|
2379
|
+
const { format } = line2;
|
|
2380
|
+
if (call !== sponsorCallRef.current) return;
|
|
2381
|
+
if (!adFitsFormat(format, colsRef.current)) return;
|
|
2382
|
+
if (format !== "top_bar" && modalOpenRef.current) return;
|
|
2383
|
+
const drawnId = `fallback:${call}:${format}`;
|
|
2384
|
+
const link = line2.clickUrl;
|
|
2385
|
+
const linkText = shortLinkText(link);
|
|
2386
|
+
const extra = linkText ? { link, linkText } : { link };
|
|
2387
|
+
if (format === "top_bar") {
|
|
2388
|
+
if (!sponsorRow(line2.text, colsRef.current, "network", linkText)) return;
|
|
2389
|
+
setSponsor({ text: line2.text, origin: "network", impressionId: drawnId, ...extra });
|
|
2390
|
+
} else {
|
|
2391
|
+
setAdSlots((slots) => ({ ...slots, [format]: { text: line2.text, impressionId: drawnId, ...extra } }));
|
|
2392
|
+
}
|
|
2393
|
+
pendingFallbackRef.current.set(format, { call, serveToken: line2.serveToken, drawnId });
|
|
2394
|
+
};
|
|
2314
2395
|
const fitting = AD_FORMATS.filter((format) => adFitsFormat(format, colsRef.current));
|
|
2315
2396
|
void (async () => {
|
|
2316
2397
|
let direct = [];
|
|
2398
|
+
let fallbacks = [];
|
|
2317
2399
|
let failed2 = false;
|
|
2318
2400
|
if (directSupportedRef.current && fitting.length > 0) {
|
|
2319
2401
|
const ask2 = sponsorDirect ?? ((formats, cols2, signal) => client.directSponsor(formats, cols2, signal));
|
|
@@ -2326,6 +2408,10 @@ function App({
|
|
|
2326
2408
|
if (!answer.supported) directSupportedRef.current = false;
|
|
2327
2409
|
failed2 = answer.failed === true;
|
|
2328
2410
|
direct = answer.lines;
|
|
2411
|
+
fallbacks = answer.fallbacks ?? [];
|
|
2412
|
+
if (answer.measure && call === sponsorCallRef.current) {
|
|
2413
|
+
measureRef.current = { call, token: answer.measure, answeredAt: Date.now(), formats: [...fitting] };
|
|
2414
|
+
}
|
|
2329
2415
|
if (!failed2) knownBookedRef.current = new Set(direct.map((line2) => line2.format));
|
|
2330
2416
|
}
|
|
2331
2417
|
if (call !== sponsorCallRef.current) return;
|
|
@@ -2335,7 +2421,7 @@ function App({
|
|
|
2335
2421
|
if (booked && "hold" in booked) continue;
|
|
2336
2422
|
if (booked) paintDirect(booked);
|
|
2337
2423
|
else if (failed2 && knownBookedRef.current.has(format)) continue;
|
|
2338
|
-
else fetchNetwork(format);
|
|
2424
|
+
else fetchNetwork(format, fallbacks.find((line2) => line2.format === format));
|
|
2339
2425
|
}
|
|
2340
2426
|
})();
|
|
2341
2427
|
},
|
|
@@ -2344,12 +2430,33 @@ function App({
|
|
|
2344
2430
|
const reportSponsor = useCallback(() => {
|
|
2345
2431
|
const pending = [...pendingImpressionRef.current.values()];
|
|
2346
2432
|
const pendingDirect = [...pendingDirectRef.current.entries()];
|
|
2433
|
+
const pendingFallback = [...pendingFallbackRef.current.entries()];
|
|
2347
2434
|
const drawn = drawnRef.current;
|
|
2435
|
+
const clock = visibilityRef.current;
|
|
2436
|
+
const linkModes = linkModeRef.current;
|
|
2437
|
+
const measure = measureRef.current;
|
|
2438
|
+
const at = Date.now();
|
|
2348
2439
|
pendingImpressionRef.current = /* @__PURE__ */ new Map();
|
|
2349
2440
|
pendingDirectRef.current = /* @__PURE__ */ new Map();
|
|
2441
|
+
pendingFallbackRef.current = /* @__PURE__ */ new Map();
|
|
2350
2442
|
drawnRef.current = /* @__PURE__ */ new Set();
|
|
2351
|
-
|
|
2443
|
+
measureRef.current = null;
|
|
2444
|
+
const directReports = pendingDirect.filter(([, p]) => p.call === sponsorCallRef.current && drawn.has(p.drawnId)).map(([format, p]) => ({
|
|
2445
|
+
format,
|
|
2446
|
+
serveToken: p.serveToken,
|
|
2447
|
+
visibleMs: clock.longest(p.drawnId, at),
|
|
2448
|
+
link: linkModes.get(p.drawnId) ?? "none"
|
|
2449
|
+
}));
|
|
2352
2450
|
if (directReports.length > 0) void client.reportDirectImpressions(directReports);
|
|
2451
|
+
if (measure && measure.call === sponsorCallRef.current) {
|
|
2452
|
+
const places = measure.formats.map((format) => ({
|
|
2453
|
+
format,
|
|
2454
|
+
visibleMs: clock.longest(placeKey(format), at, measure.answeredAt)
|
|
2455
|
+
}));
|
|
2456
|
+
void client.reportSponsorViews(measure.token, places);
|
|
2457
|
+
}
|
|
2458
|
+
const fallbackReports = pendingFallback.filter(([, p]) => p.call === sponsorCallRef.current && drawn.has(p.drawnId)).map(([format, p]) => ({ format, serveToken: p.serveToken }));
|
|
2459
|
+
if (fallbackReports.length > 0) void client.reportAffiliateImpressions(fallbackReports);
|
|
2353
2460
|
const reports = pending.filter((p) => p.call === sponsorCallRef.current).filter((p) => drawn.has(p.impression.impressionId)).map((p) => p.impression);
|
|
2354
2461
|
if (reports.length === 0) return;
|
|
2355
2462
|
void (async () => {
|
|
@@ -3991,13 +4098,13 @@ ${dropped.map((l) => ` \xB7 ${l}`).join("\n")}`
|
|
|
3991
4098
|
});
|
|
3992
4099
|
const elapsed = busy && startedAt ? Math.floor((Date.now() - startedAt) / 1e3) : 0;
|
|
3993
4100
|
const spinner = spinnerRef.current[Math.floor(tick / SPINNER_TICKS_PER_FRAME) % spinnerRef.current.length];
|
|
3994
|
-
const sponsorLine = busy && sponsor ? sponsorRow(sponsor.text, cols, sponsor.origin) : null;
|
|
4101
|
+
const sponsorLine = busy && sponsor ? sponsorRow(sponsor.text, cols, sponsor.origin, sponsor.linkText, sponsor.linkRequired === true) : null;
|
|
3995
4102
|
const adColor = adColorEnabled({ isTTY: stdout.isTTY, env: process.env });
|
|
3996
4103
|
const linkable = hyperlinksEnabled({ isTTY: stdout.isTTY, env: process.env });
|
|
3997
4104
|
const modalOpen = Boolean(picker || ask || plan || buy);
|
|
3998
4105
|
modalOpenRef.current = modalOpen;
|
|
3999
|
-
const card = busy && !modalOpen && adSlots.sidebar_card ? sidebarCard(adSlots.sidebar_card.text, cols) : null;
|
|
4000
|
-
const statusBarLine = busy && !modalOpen && adSlots.status_bar ? sponsorRow(adSlots.status_bar.text, cols, "network") : null;
|
|
4106
|
+
const card = busy && !modalOpen && adSlots.sidebar_card ? sidebarCard(adSlots.sidebar_card.text, cols, adSlots.sidebar_card.linkText) : null;
|
|
4107
|
+
const statusBarLine = busy && !modalOpen && adSlots.status_bar ? sponsorRow(adSlots.status_bar.text, cols, "network", adSlots.status_bar.linkText, adSlots.status_bar.linkRequired === true) : null;
|
|
4001
4108
|
const viewport = Math.max(1, rows - 1);
|
|
4002
4109
|
const inputRows = Math.max(1, Math.min(MAX_INPUT_ROWS, editor.lines.length));
|
|
4003
4110
|
const inputFrom = windowStart(editor.row, editor.lines.length, inputRows);
|
|
@@ -4028,9 +4135,24 @@ ${dropped.map((l) => ` \xB7 ${l}`).join("\n")}`
|
|
|
4028
4135
|
showCard ? adSlots.sidebar_card?.impressionId : void 0,
|
|
4029
4136
|
showStatusBar ? adSlots.status_bar?.impressionId : void 0
|
|
4030
4137
|
].filter(Boolean).join(" ");
|
|
4138
|
+
const placesKey = [
|
|
4139
|
+
showSponsor && sponsorLine ? placeKey("top_bar") : void 0,
|
|
4140
|
+
showCard ? placeKey("sidebar_card") : void 0,
|
|
4141
|
+
showStatusBar ? placeKey("status_bar") : void 0
|
|
4142
|
+
].filter(Boolean).join(" ");
|
|
4143
|
+
if (showSponsor && sponsorLine && sponsor?.impressionId?.startsWith("direct:")) {
|
|
4144
|
+
linkModeRef.current.set(sponsor.impressionId, linkable ? "link" : sponsorLine.link ? "address" : "none");
|
|
4145
|
+
}
|
|
4146
|
+
if (showCard && card && adSlots.sidebar_card?.impressionId.startsWith("direct:")) {
|
|
4147
|
+
linkModeRef.current.set(adSlots.sidebar_card.impressionId, linkable ? "link" : card.link ? "address" : "none");
|
|
4148
|
+
}
|
|
4149
|
+
if (showStatusBar && statusBarLine && adSlots.status_bar?.impressionId.startsWith("direct:")) {
|
|
4150
|
+
linkModeRef.current.set(adSlots.status_bar.impressionId, linkable ? "link" : statusBarLine.link ? "address" : "none");
|
|
4151
|
+
}
|
|
4031
4152
|
useEffect(() => {
|
|
4032
4153
|
for (const id of drawnKey.split(" ")) if (id) drawnRef.current.add(id);
|
|
4033
|
-
|
|
4154
|
+
visibilityRef.current.observe([...drawnKey.split(" "), ...placesKey.split(" ")].filter(Boolean), Date.now());
|
|
4155
|
+
}, [drawnKey, placesKey]);
|
|
4034
4156
|
let budget = Math.max(0, viewport - chromeHeight - menu.length - busyHeight);
|
|
4035
4157
|
const pickerCompact = Boolean(picker) && budget < PICKER_CHROME + 1;
|
|
4036
4158
|
const pickerChrome = pickerCompact ? PICKER_CHROME_COMPACT : PICKER_CHROME;
|
|
@@ -4276,7 +4398,11 @@ ${dropped.map((l) => ` \xB7 ${l}`).join("\n")}`
|
|
|
4276
4398
|
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: SIDEBAR_CARD_COLS, children: [
|
|
4277
4399
|
/* @__PURE__ */ jsxs2(Box2, { children: [
|
|
4278
4400
|
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: card.rule }),
|
|
4279
|
-
/* @__PURE__ */ jsx2(Text2, { ...adColor ? { color: AD_MARK } : { dimColor: true }, wrap: "truncate-end", children: card.label })
|
|
4401
|
+
/* @__PURE__ */ jsx2(Text2, { ...adColor ? { color: AD_MARK } : { dimColor: true }, wrap: "truncate-end", children: card.label }),
|
|
4402
|
+
card.link ? /* @__PURE__ */ jsxs2(Text2, { ...adColor ? { color: AD_TEXT } : { dimColor: true }, wrap: "truncate-end", children: [
|
|
4403
|
+
LINK_SEPARATOR,
|
|
4404
|
+
/* @__PURE__ */ jsx2(Text2, { underline: adColor, children: linkable && adSlots.sidebar_card?.link ? osc8(card.link, adSlots.sidebar_card.link) : card.link })
|
|
4405
|
+
] }) : null
|
|
4280
4406
|
] }),
|
|
4281
4407
|
/* @__PURE__ */ jsxs2(Box2, { children: [
|
|
4282
4408
|
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: card.rule }),
|
|
@@ -4365,7 +4491,13 @@ function SponsorRowView({ row, adColor, link }) {
|
|
|
4365
4491
|
return /* @__PURE__ */ jsxs2(Box2, { children: [
|
|
4366
4492
|
/* @__PURE__ */ jsx2(Text2, { dimColor: true, wrap: "truncate-end", children: row.marker }),
|
|
4367
4493
|
row.label ? /* @__PURE__ */ jsx2(Text2, { ...adColor ? { color: AD_MARK } : { dimColor: true }, wrap: "truncate-end", children: row.label }) : null,
|
|
4368
|
-
/* @__PURE__ */
|
|
4494
|
+
/* @__PURE__ */ jsxs2(Text2, { ...adColor && row.origin === "network" ? { color: AD_TEXT } : { dimColor: true }, wrap: "truncate-end", children: [
|
|
4495
|
+
link && row.origin === "network" ? osc8(row.body, link) : row.body,
|
|
4496
|
+
row.link ? /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
4497
|
+
LINK_SEPARATOR,
|
|
4498
|
+
/* @__PURE__ */ jsx2(Text2, { underline: adColor, children: link ? osc8(row.link, link) : row.link })
|
|
4499
|
+
] }) : null
|
|
4500
|
+
] })
|
|
4369
4501
|
] });
|
|
4370
4502
|
}
|
|
4371
4503
|
function sleep(ms, signal) {
|