ytracking-web 0.1.2 → 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 CHANGED
@@ -28,24 +28,44 @@ npm install ytracking-web
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
- baseUrls: ["https://你的網域"],
37
- fallbackBaseUrls: [],
38
- endpointListTtlSec: 300,
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)` — 必填 `siteId`、`baseUrls`(無尾階 `/` 之 origin)
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`)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ytracking-web",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "description": "Browser SDK for YTracking collect/install (IndexedDB queue, retry, multi-host)",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
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
  }
@@ -51,7 +88,10 @@ function attributionTokenUrl(origin: string): string {
51
88
 
52
89
  export class YTrackingWebClient {
53
90
  private cfg: Required<
54
- Pick<InitConfig, "siteId" | "baseUrls"> & {
91
+ Pick<InitConfig, "siteId"> & {
92
+ baseUrls: string[];
93
+ sdkKey: string | undefined;
94
+ autoTrackPageView: boolean;
55
95
  fallbackBaseUrls: string[];
56
96
  debug: boolean;
57
97
  enableVisitorFallbackFingerprint: boolean;
@@ -83,15 +123,18 @@ export class YTrackingWebClient {
83
123
  } = {};
84
124
 
85
125
  constructor(cfg: InitConfig) {
86
- const base = normalizeOrigins(cfg.baseUrls);
126
+ const base = mergeBaseOrigins(cfg);
87
127
  if (!cfg.siteId?.trim() || base.length === 0) {
88
128
  throw new Error(
89
- "YTrackingWeb: siteId and at least one baseUrl are required",
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)",
90
130
  );
91
131
  }
132
+ const sk = cfg.sdkKey?.trim();
92
133
  this.cfg = {
93
134
  siteId: cfg.siteId.trim(),
94
135
  baseUrls: base,
136
+ sdkKey: sk || undefined,
137
+ autoTrackPageView: cfg.autoTrackPageView !== false,
95
138
  fallbackBaseUrls: normalizeOrigins(cfg.fallbackBaseUrls ?? []),
96
139
  debug: !!cfg.debug,
97
140
  enableVisitorFallbackFingerprint: !!cfg.enableVisitorFallbackFingerprint,
@@ -110,6 +153,12 @@ export class YTrackingWebClient {
110
153
  ];
111
154
  }
112
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
+
113
162
  setContext(partial: {
114
163
  visitorId?: string;
115
164
  sessionId?: string;
@@ -150,6 +199,9 @@ export class YTrackingWebClient {
150
199
  document.addEventListener("visibilitychange", this.visibilityHandler);
151
200
  }
152
201
  void this.flush();
202
+ if (this.cfg.autoTrackPageView) {
203
+ void this.trackPageView();
204
+ }
153
205
  }
154
206
 
155
207
  shutdown(): void {
@@ -244,6 +296,7 @@ export class YTrackingWebClient {
244
296
  payload,
245
297
  this.cfg.requestTimeoutMs,
246
298
  undefined,
299
+ this.sdkHeaders(),
247
300
  );
248
301
  if (!res.ok) {
249
302
  log(
@@ -456,6 +509,7 @@ export class YTrackingWebClient {
456
509
  rec.payload,
457
510
  this.cfg.requestTimeoutMs,
458
511
  idempotencyForRequest,
512
+ this.sdkHeaders(),
459
513
  );
460
514
  if (!res.ok) {
461
515
  log(this.cfg.debug, "post transport fail", rec.id, origin, res.kind);
@@ -563,6 +617,8 @@ export class YTrackingWebClient {
563
617
  `${origin}/v1/sdk/domain-health/report`,
564
618
  payload,
565
619
  this.cfg.requestTimeoutMs,
620
+ undefined,
621
+ this.sdkHeaders(),
566
622
  );
567
623
  }
568
624
 
package/src/transport.ts CHANGED
@@ -24,11 +24,18 @@ 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",
package/src/types.ts CHANGED
@@ -13,8 +13,25 @@ export type OutboxRecord = {
13
13
 
14
14
  export type InitConfig = {
15
15
  siteId: string;
16
- baseUrls: string[];
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;