ytracking-web 0.1.1 → 0.2.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 +31 -10
- package/package.json +1 -1
- package/src/client.ts +173 -3
- package/src/index.ts +1 -0
- package/src/transport.ts +10 -1
- package/src/types.ts +18 -1
package/README.md
CHANGED
|
@@ -21,31 +21,51 @@ npm install ytracking-web
|
|
|
21
21
|
|
|
22
22
|
## 發佈至 npm
|
|
23
23
|
|
|
24
|
-
1.
|
|
24
|
+
1. 於倉庫根目錄先跑契約檢查:`npm run check:web-sdk`、`npm run check:openapi-drift`、(建議)`npm run build:web-sdk`。(本機 **`npm run publish:ytracking-web`** 已內含 `check:web-sdk` 與 `check:openapi-drift`。)
|
|
25
25
|
2. **GitHub Actions**:「Publish ytracking-web to npm」workflow(`workflow_dispatch`)。需在 repo 設定 **`NPM_TOKEN`**(npm automation access token,具 publish 權限)。
|
|
26
|
-
3. **本機(免 `npm login`,適合首次發佈作法 B)**:在倉庫根目錄設定 **`NPM_TOKEN`** 後執行 **`npm run publish:ytracking-web`**(會先跑 `check:web-sdk` 再 `npm publish
|
|
26
|
+
3. **本機(免 `npm login`,適合首次發佈作法 B)**:在倉庫根目錄設定 **`NPM_TOKEN`** 後執行 **`npm run publish:ytracking-web`**(會先跑 `check:web-sdk` 再 `npm publish`)。若有 API 契約改動,請先在 root 執行 `npm run check:openapi-drift` 並同步更新 `docs/API.md` / `docs/openapi.yaml`。
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
## 使用(IIFE)
|
|
30
30
|
|
|
31
|
+
**最少程式(腳本與 API 同源、後台未強制 collect 金鑰時):** 只需 `siteId`;SDK 會從 `<script src="…ytracking-web.js">` 推斷 ingest origin,並在啟動後自動送一次 `page_view`。
|
|
32
|
+
|
|
31
33
|
```html
|
|
32
34
|
<script src="https://你的網域/sdk/ytracking-web.js"></script>
|
|
35
|
+
<script>
|
|
36
|
+
YTrackingWeb.init({ siteId: "站點-UUID" });
|
|
37
|
+
</script>
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**明確指定 API 網址或備援、或帶 SDK key(與行動端一致):**
|
|
41
|
+
|
|
42
|
+
```html
|
|
43
|
+
<script src="https://cdn.example.com/sdk/ytracking-web.js"></script>
|
|
33
44
|
<script>
|
|
34
45
|
YTrackingWeb.init({
|
|
35
46
|
siteId: "站點-UUID",
|
|
36
|
-
|
|
37
|
-
fallbackBaseUrls: [],
|
|
38
|
-
|
|
39
|
-
domainHealthReportMinIntervalMs: 300000,
|
|
40
|
-
debug: false,
|
|
47
|
+
baseUrl: "https://ingest.example.com",
|
|
48
|
+
fallbackBaseUrls: ["https://ingest-backup.example.com"],
|
|
49
|
+
sdkKey: "yt_sdk_…",
|
|
41
50
|
});
|
|
42
|
-
YTrackingWeb.trackPageView();
|
|
43
51
|
</script>
|
|
44
52
|
```
|
|
45
53
|
|
|
54
|
+
若腳本託管在 CDN 而 API 在另一網域,**必須**設定 `baseUrl` 或 `baseUrls`(不可依賴推斷)。
|
|
55
|
+
僅追蹤 SPA 路由、不要自動首屏 `page_view` 時:加 **`autoTrackPageView: false`**,再自行呼叫 `trackPageView()`。
|
|
56
|
+
|
|
57
|
+
### 0.2.0 行為變更(升級自 0.1.x)
|
|
58
|
+
|
|
59
|
+
- 預設 **`autoTrackPageView: true`**:若你原本在 `init` 後又手動呼叫一次 `trackPageView()`,首屏可能重複計數 — 請刪除多餘呼叫,或設 `autoTrackPageView: false`。
|
|
60
|
+
- `baseUrls` 改為可選:可只用 **`baseUrl`**(字串),或与 `baseUrls` 合併(`baseUrl` 優先)。
|
|
61
|
+
|
|
46
62
|
## API(`YTrackingWeb`)
|
|
47
63
|
|
|
48
|
-
- `init(config)` — 必填
|
|
64
|
+
- `init(config)` — 必填 **`siteId`**
|
|
65
|
+
- **`baseUrl`**:單一 ingest origin(無尾階 `/`);與 **`baseUrls`** 可併用(順序:先 `baseUrl` 再 `baseUrls`)
|
|
66
|
+
- **`baseUrls`**:ingest origins 陣列;若與 `baseUrl` 皆省略,會嘗試從頁面上最後一個 **`…ytracking-web(.min).js`** 的 `<script src>` 推斷 origin(腳本須與 API 同源才正確)
|
|
67
|
+
- **`sdkKey`**:若設定,所有 POST(collect、install、attribution token、domain-health report)會帶 **`X-YT-Sdk-Key`**
|
|
68
|
+
- **`autoTrackPageView`**:預設 **`true`**,`start()` 後自動 enqueue 一次 `page_view`;設 `false` 則完全自行呼叫 `trackPageView`/`track`
|
|
49
69
|
- `enableVisitorFallbackFingerprint`:可選(預設 `false`),僅在無 cookie + 無 localStorage visitor 時以低熵指紋產生暫時 visitor seed
|
|
50
70
|
- `maxQueueSize`:本地 queue 容量上限(預設 `500`),超量時淘汰低優先且最舊事件
|
|
51
71
|
- `batchSize`:單次 flush 最多取件數(預設 `8`)
|
|
@@ -55,6 +75,7 @@ npm install ytracking-web
|
|
|
55
75
|
- `track(type, { props })` — 對應 `POST /v1/collect` envelope
|
|
56
76
|
- `trackPageView()` — `page_view`
|
|
57
77
|
- `install({ clickId? })` — 內嵌 **`POST /v1/install`**(無 `appId` 之 embed 形態)
|
|
78
|
+
- `issueAttributionToken({ clickId?, ttlSeconds?, campaignId?, appId?, deferredContext? })` — 同步 **`POST /v1/attribution/token`**(不入佇列);沿用 `setContext` 之 visitor/session/click 等
|
|
58
79
|
- `flush()` — 手動送出佇列
|
|
59
80
|
- `setContext({ visitorId, sessionId, clickId, campaignId, appId })` — 後續事件帶入
|
|
60
81
|
- `shutdown()` — 停止定時 flush
|
|
@@ -64,7 +85,7 @@ npm install ytracking-web
|
|
|
64
85
|
- 每筆事件在 `event.props` 附 **`_ytSdkEventId`**(支援 `crypto.randomUUID` 時為 UUID)與 **`_ytSdk: "web-v1"`**;同時在 JSON 根層帶 **`idempotencyKey`**(與該 UUID 相同),與 **`POST /v1/collect`** 去重契約對齊(見 [API.md](../../docs/API.md) §2.2)。重試/重送同一佇列項目時沿用同一 id,consumer 只會落庫一次。
|
|
65
86
|
- `collect` 出站 `Idempotency-Key` 會優先使用 payload 根層 `idempotencyKey`(若缺失才回退 queue id),確保 header 與 body 同鍵。
|
|
66
87
|
- collect 成功回應若包含 `visitorId`,SDK 會寫入 localStorage(`yt_vid_v1`)並自動帶入後續事件 context;可在 cookie 受限環境維持 visitor 關聯。
|
|
67
|
-
- 須符合 `sites.allowed_origins`(見 [SITE_AND_EMBED.md](../../docs/SITE_AND_EMBED.md))。
|
|
88
|
+
- 須符合 `sites.allowed_origins`(見 [SITE_AND_EMBED.md](../../docs/SITE_AND_EMBED.md))。SDK collect 出站採 `credentials: "omit"`,不依賴瀏覽器 cookie。
|
|
68
89
|
- fallback 會維護 endpoint pool + cursor;成功入口可刷新新 hosts,不可達時會送 best-effort `domain-health/report`(含節流)。
|
|
69
90
|
- `Retry-After` 同時支援秒數與 HTTP-date;`shutdown()` 會解除 `visibilitychange` listener,避免重複初始化時累積監聽器。
|
|
70
91
|
|
package/package.json
CHANGED
package/src/client.ts
CHANGED
|
@@ -37,6 +37,43 @@ function normalizeOrigins(urls: string[]): string[] {
|
|
|
37
37
|
return out;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
/** When `baseUrl` / `baseUrls` omitted, use the origin of the last ytracking-web script tag. */
|
|
41
|
+
function inferBaseUrlFromLastYTrackingScript(): string | undefined {
|
|
42
|
+
if (typeof document === "undefined") return undefined;
|
|
43
|
+
const scripts = document.getElementsByTagName("script");
|
|
44
|
+
for (let i = scripts.length - 1; i >= 0; i -= 1) {
|
|
45
|
+
const el = scripts.item(i);
|
|
46
|
+
if (!el || el.nodeName !== "SCRIPT") continue;
|
|
47
|
+
const src = (el as HTMLScriptElement).src;
|
|
48
|
+
if (!src) continue;
|
|
49
|
+
try {
|
|
50
|
+
const baseHref =
|
|
51
|
+
typeof location !== "undefined" && location.href
|
|
52
|
+
? location.href
|
|
53
|
+
: "https://invalid.invalid/";
|
|
54
|
+
const u = new URL(src, baseHref);
|
|
55
|
+
if (/ytracking-web(\.min)?\.js([?#]|$)/i.test(u.pathname)) {
|
|
56
|
+
return u.origin;
|
|
57
|
+
}
|
|
58
|
+
} catch {
|
|
59
|
+
/* ignore */
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function mergeBaseOrigins(cfg: InitConfig): string[] {
|
|
66
|
+
const parts: string[] = [];
|
|
67
|
+
if (cfg.baseUrl) parts.push(cfg.baseUrl);
|
|
68
|
+
if (cfg.baseUrls?.length) parts.push(...cfg.baseUrls);
|
|
69
|
+
let base = normalizeOrigins(parts);
|
|
70
|
+
if (base.length === 0) {
|
|
71
|
+
const inferred = inferBaseUrlFromLastYTrackingScript();
|
|
72
|
+
if (inferred) base = [inferred];
|
|
73
|
+
}
|
|
74
|
+
return [...new Set(base)];
|
|
75
|
+
}
|
|
76
|
+
|
|
40
77
|
function collectUrl(origin: string): string {
|
|
41
78
|
return `${origin}/v1/collect`;
|
|
42
79
|
}
|
|
@@ -45,9 +82,16 @@ function installUrl(origin: string): string {
|
|
|
45
82
|
return `${origin}/v1/install`;
|
|
46
83
|
}
|
|
47
84
|
|
|
85
|
+
function attributionTokenUrl(origin: string): string {
|
|
86
|
+
return `${origin}/v1/attribution/token`;
|
|
87
|
+
}
|
|
88
|
+
|
|
48
89
|
export class YTrackingWebClient {
|
|
49
90
|
private cfg: Required<
|
|
50
|
-
Pick<InitConfig, "siteId"
|
|
91
|
+
Pick<InitConfig, "siteId"> & {
|
|
92
|
+
baseUrls: string[];
|
|
93
|
+
sdkKey: string | undefined;
|
|
94
|
+
autoTrackPageView: boolean;
|
|
51
95
|
fallbackBaseUrls: string[];
|
|
52
96
|
debug: boolean;
|
|
53
97
|
enableVisitorFallbackFingerprint: boolean;
|
|
@@ -79,15 +123,18 @@ export class YTrackingWebClient {
|
|
|
79
123
|
} = {};
|
|
80
124
|
|
|
81
125
|
constructor(cfg: InitConfig) {
|
|
82
|
-
const base =
|
|
126
|
+
const base = mergeBaseOrigins(cfg);
|
|
83
127
|
if (!cfg.siteId?.trim() || base.length === 0) {
|
|
84
128
|
throw new Error(
|
|
85
|
-
"YTrackingWeb: siteId and at least one
|
|
129
|
+
"YTrackingWeb: siteId and at least one ingest origin are required (set baseUrl or baseUrls, or load the SDK from your API host so the script origin can be inferred)",
|
|
86
130
|
);
|
|
87
131
|
}
|
|
132
|
+
const sk = cfg.sdkKey?.trim();
|
|
88
133
|
this.cfg = {
|
|
89
134
|
siteId: cfg.siteId.trim(),
|
|
90
135
|
baseUrls: base,
|
|
136
|
+
sdkKey: sk || undefined,
|
|
137
|
+
autoTrackPageView: cfg.autoTrackPageView !== false,
|
|
91
138
|
fallbackBaseUrls: normalizeOrigins(cfg.fallbackBaseUrls ?? []),
|
|
92
139
|
debug: !!cfg.debug,
|
|
93
140
|
enableVisitorFallbackFingerprint: !!cfg.enableVisitorFallbackFingerprint,
|
|
@@ -106,6 +153,12 @@ export class YTrackingWebClient {
|
|
|
106
153
|
];
|
|
107
154
|
}
|
|
108
155
|
|
|
156
|
+
private sdkHeaders(): Record<string, string> | undefined {
|
|
157
|
+
const k = this.cfg.sdkKey;
|
|
158
|
+
if (!k) return undefined;
|
|
159
|
+
return { "X-YT-Sdk-Key": k };
|
|
160
|
+
}
|
|
161
|
+
|
|
109
162
|
setContext(partial: {
|
|
110
163
|
visitorId?: string;
|
|
111
164
|
sessionId?: string;
|
|
@@ -146,6 +199,9 @@ export class YTrackingWebClient {
|
|
|
146
199
|
document.addEventListener("visibilitychange", this.visibilityHandler);
|
|
147
200
|
}
|
|
148
201
|
void this.flush();
|
|
202
|
+
if (this.cfg.autoTrackPageView) {
|
|
203
|
+
void this.trackPageView();
|
|
204
|
+
}
|
|
149
205
|
}
|
|
150
206
|
|
|
151
207
|
shutdown(): void {
|
|
@@ -200,6 +256,106 @@ export class YTrackingWebClient {
|
|
|
200
256
|
});
|
|
201
257
|
}
|
|
202
258
|
|
|
259
|
+
/**
|
|
260
|
+
* Issue a one-time deferred deep link token (`POST /v1/attribution/token`).
|
|
261
|
+
* Synchronous HTTP (not queued); rotates ingestion origins on success like collect/install.
|
|
262
|
+
*/
|
|
263
|
+
async issueAttributionToken(opts?: {
|
|
264
|
+
clickId?: string;
|
|
265
|
+
ttlSeconds?: number;
|
|
266
|
+
campaignId?: string;
|
|
267
|
+
appId?: string;
|
|
268
|
+
deferredContext?: Record<string, unknown>;
|
|
269
|
+
}): Promise<{ attributionToken: string; expiresAt: string }> {
|
|
270
|
+
const body: Record<string, unknown> = { siteId: this.cfg.siteId };
|
|
271
|
+
const cid = opts?.clickId?.trim() || this.context.clickId?.trim();
|
|
272
|
+
if (cid) body.clickId = cid;
|
|
273
|
+
const vid = this.context.visitorId?.trim();
|
|
274
|
+
if (vid) body.visitorId = vid;
|
|
275
|
+
const sid = this.context.sessionId?.trim();
|
|
276
|
+
if (sid) body.sessionId = sid;
|
|
277
|
+
const camp = opts?.campaignId?.trim() || this.context.campaignId?.trim();
|
|
278
|
+
if (camp) body.campaignId = camp;
|
|
279
|
+
const aid = opts?.appId?.trim() || this.context.appId?.trim();
|
|
280
|
+
if (aid) body.appId = aid;
|
|
281
|
+
if (opts?.ttlSeconds != null) body.ttlSeconds = opts.ttlSeconds;
|
|
282
|
+
if (
|
|
283
|
+
opts?.deferredContext &&
|
|
284
|
+
typeof opts.deferredContext === "object" &&
|
|
285
|
+
!Array.isArray(opts.deferredContext)
|
|
286
|
+
) {
|
|
287
|
+
body.deferredContext = opts.deferredContext;
|
|
288
|
+
}
|
|
289
|
+
const payload = JSON.stringify(body);
|
|
290
|
+
let lastRetryAfter: number | undefined;
|
|
291
|
+
|
|
292
|
+
for (const origin of this.originsInOrder()) {
|
|
293
|
+
const url = attributionTokenUrl(origin);
|
|
294
|
+
const res = await postJson(
|
|
295
|
+
url,
|
|
296
|
+
payload,
|
|
297
|
+
this.cfg.requestTimeoutMs,
|
|
298
|
+
undefined,
|
|
299
|
+
this.sdkHeaders(),
|
|
300
|
+
);
|
|
301
|
+
if (!res.ok) {
|
|
302
|
+
log(
|
|
303
|
+
this.cfg.debug,
|
|
304
|
+
"issueAttributionToken transport fail",
|
|
305
|
+
origin,
|
|
306
|
+
res.kind,
|
|
307
|
+
);
|
|
308
|
+
if (res.retryAfterMs !== undefined) lastRetryAfter = res.retryAfterMs;
|
|
309
|
+
if (res.kind === "network" || res.kind === "timeout") {
|
|
310
|
+
await this.reportDomainUnreachable(origin, res.kind);
|
|
311
|
+
this.rotateCursorAfter(origin);
|
|
312
|
+
}
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
if (res.retryAfterMs !== undefined) lastRetryAfter = res.retryAfterMs;
|
|
316
|
+
|
|
317
|
+
if (res.status === 201) {
|
|
318
|
+
const j = res.bodyJson as {
|
|
319
|
+
attributionToken?: unknown;
|
|
320
|
+
expiresAt?: unknown;
|
|
321
|
+
};
|
|
322
|
+
const tok =
|
|
323
|
+
typeof j?.attributionToken === "string"
|
|
324
|
+
? j.attributionToken.trim()
|
|
325
|
+
: "";
|
|
326
|
+
const exp = typeof j?.expiresAt === "string" ? j.expiresAt.trim() : "";
|
|
327
|
+
if (tok && exp) {
|
|
328
|
+
this.setCursorToOrigin(origin);
|
|
329
|
+
await this.refreshEndpointPool(origin);
|
|
330
|
+
return { attributionToken: tok, expiresAt: exp };
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
if (isNonRetryableStatus(res.status)) {
|
|
334
|
+
throw new Error(
|
|
335
|
+
`YTrackingWeb: issueAttributionToken failed with HTTP ${res.status}`,
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
if (isRetryableStatus(res.status)) {
|
|
339
|
+
log(
|
|
340
|
+
this.cfg.debug,
|
|
341
|
+
"issueAttributionToken retryable",
|
|
342
|
+
res.status,
|
|
343
|
+
origin,
|
|
344
|
+
);
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
throw new Error(
|
|
348
|
+
`YTrackingWeb: issueAttributionToken failed with HTTP ${res.status}`,
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
throw new Error(
|
|
353
|
+
lastRetryAfter !== undefined
|
|
354
|
+
? `YTrackingWeb: issueAttributionToken failed (Retry-After hint ${lastRetryAfter}ms)`
|
|
355
|
+
: "YTrackingWeb: issueAttributionToken failed on all origins",
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
|
|
203
359
|
private async enqueue(p: {
|
|
204
360
|
kind: OutboxRecord["kind"];
|
|
205
361
|
payload: string;
|
|
@@ -353,6 +509,7 @@ export class YTrackingWebClient {
|
|
|
353
509
|
rec.payload,
|
|
354
510
|
this.cfg.requestTimeoutMs,
|
|
355
511
|
idempotencyForRequest,
|
|
512
|
+
this.sdkHeaders(),
|
|
356
513
|
);
|
|
357
514
|
if (!res.ok) {
|
|
358
515
|
log(this.cfg.debug, "post transport fail", rec.id, origin, res.kind);
|
|
@@ -460,6 +617,8 @@ export class YTrackingWebClient {
|
|
|
460
617
|
`${origin}/v1/sdk/domain-health/report`,
|
|
461
618
|
payload,
|
|
462
619
|
this.cfg.requestTimeoutMs,
|
|
620
|
+
undefined,
|
|
621
|
+
this.sdkHeaders(),
|
|
463
622
|
);
|
|
464
623
|
}
|
|
465
624
|
|
|
@@ -567,6 +726,17 @@ export async function install(opts?: { clickId?: string }): Promise<void> {
|
|
|
567
726
|
return singleton.install(opts);
|
|
568
727
|
}
|
|
569
728
|
|
|
729
|
+
export async function issueAttributionToken(opts?: {
|
|
730
|
+
clickId?: string;
|
|
731
|
+
ttlSeconds?: number;
|
|
732
|
+
campaignId?: string;
|
|
733
|
+
appId?: string;
|
|
734
|
+
deferredContext?: Record<string, unknown>;
|
|
735
|
+
}): Promise<{ attributionToken: string; expiresAt: string }> {
|
|
736
|
+
if (!singleton) throw new Error("YTrackingWeb: call init() first");
|
|
737
|
+
return singleton.issueAttributionToken(opts);
|
|
738
|
+
}
|
|
739
|
+
|
|
570
740
|
export function setContext(partial: {
|
|
571
741
|
visitorId?: string;
|
|
572
742
|
sessionId?: string;
|
package/src/index.ts
CHANGED
package/src/transport.ts
CHANGED
|
@@ -24,17 +24,26 @@ export async function postJson(
|
|
|
24
24
|
body: string,
|
|
25
25
|
timeoutMs: number,
|
|
26
26
|
idempotencyKey?: string,
|
|
27
|
+
extraHeaders?: Record<string, string>,
|
|
27
28
|
): Promise<PostResult> {
|
|
28
29
|
const ctrl = new AbortController();
|
|
29
30
|
const t = setTimeout(() => ctrl.abort(), timeoutMs);
|
|
30
31
|
try {
|
|
31
32
|
const hdrs: Record<string, string> = { "Content-Type": "application/json" };
|
|
33
|
+
if (extraHeaders) {
|
|
34
|
+
for (const [k, v] of Object.entries(extraHeaders)) {
|
|
35
|
+
const key = k?.trim();
|
|
36
|
+
if (key) hdrs[key] = String(v);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
32
39
|
if (idempotencyKey) hdrs["Idempotency-Key"] = idempotencyKey;
|
|
33
40
|
const res = await fetch(url, {
|
|
34
41
|
method: "POST",
|
|
35
42
|
headers: hdrs,
|
|
36
43
|
body,
|
|
37
|
-
|
|
44
|
+
// Collect does not depend on cookies. Keep this non-credentialed so
|
|
45
|
+
// wildcard ACAO ("*") deployments remain browser-compatible.
|
|
46
|
+
credentials: "omit",
|
|
38
47
|
signal: ctrl.signal,
|
|
39
48
|
keepalive: true,
|
|
40
49
|
});
|
package/src/types.ts
CHANGED
|
@@ -13,8 +13,25 @@ export type OutboxRecord = {
|
|
|
13
13
|
|
|
14
14
|
export type InitConfig = {
|
|
15
15
|
siteId: string;
|
|
16
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Primary ingest origin (no trailing slash). Merged with `baseUrls` (order: baseUrl first).
|
|
18
|
+
* If neither `baseUrl` nor `baseUrls` is set, the SDK tries to infer the origin from the last
|
|
19
|
+
* `<script src="...ytracking-web(.min).js">` on the page (same host as your deployed `/sdk/` script).
|
|
20
|
+
*/
|
|
21
|
+
baseUrl?: string;
|
|
22
|
+
/** Ingest origins (no trailing slashes). Use with or instead of singular `baseUrl`. */
|
|
23
|
+
baseUrls?: string[];
|
|
17
24
|
fallbackBaseUrls?: string[];
|
|
25
|
+
/**
|
|
26
|
+
* When set, all POSTs (collect, install, attribution token, domain-health report) include
|
|
27
|
+
* `X-YT-Sdk-Key` — same as mobile SDKs; required if your site enforces collect auth.
|
|
28
|
+
*/
|
|
29
|
+
sdkKey?: string;
|
|
30
|
+
/**
|
|
31
|
+
* After IndexedDB/outbox starts, enqueue one `page_view` (default **true**).
|
|
32
|
+
* Set `false` if you only track SPA navigations or call `trackPageView()` yourself.
|
|
33
|
+
*/
|
|
34
|
+
autoTrackPageView?: boolean;
|
|
18
35
|
debug?: boolean;
|
|
19
36
|
enableVisitorFallbackFingerprint?: boolean;
|
|
20
37
|
maxQueueSize?: number;
|