@tengence/track-sdk-web 1.0.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 ADDED
@@ -0,0 +1 @@
1
+ **@tengence/track-sdk-web**
@@ -0,0 +1,18 @@
1
+ import { BaseOptions, ClientReportData, ReportFunction } from "./interface";
2
+ declare global {
3
+ interface Window {
4
+ trackMonitor: {
5
+ report: ReportFunction;
6
+ realtimeReport: ReportFunction;
7
+ bindUser: (id: string) => void;
8
+ unBindUser: () => void;
9
+ };
10
+ }
11
+ }
12
+ declare const init: (options: BaseOptions) => {
13
+ bindUser: (id: string) => void;
14
+ unBindUser: () => void;
15
+ report: (data: ClientReportData) => void;
16
+ realtimeReport: (data: ClientReportData) => void;
17
+ };
18
+ export default init;
package/dist/index.mjs ADDED
@@ -0,0 +1 @@
1
+ const e=!!navigator?.sendBeacon,t=window.requestIdleCallback||window.requestAnimationFrame||(e=>setTimeout(e,17)),n=e=>{"complete"===document.readyState?setTimeout(e):window.addEventListener("pageshow",e,{once:!0,capture:!0})};function r(e,t=150,n=!1){let r=null;return function(...o){const a=this,i=n&&!r;r&&clearTimeout(r),r=setTimeout((()=>{r=null,n||e.apply(a,o)}),t),i&&e.apply(a,o)}}const o=(e,t=300)=>{let n=0;return function(...r){const o=Date.now();o-n>=t&&(n=o,e.apply(this,r))}};function a(e,t=""){if(!e)return t;const n=e.parentElement,{id:r}=e,o=e.tagName.toLowerCase(),i=t?`>${t}`:"";return n&&n!==document.documentElement&&n!==document.body?r?`#${r}${i}`:a(n,`${o}${i}`):`${o}${i}`}const i=()=>{if(window.performance?.navigation)return performance.navigation.type;if(window.performance?.getEntriesByType){const e=performance.getEntriesByType("navigation");if(e.length>0)return e[0].type}return 255},s=e=>{const t=history[e];return function(){const n=t.apply(this,arguments),r=new Event(e);return window.dispatchEvent(r),n}},c=e=>{window.addEventListener("replaceState",(t=>e(t)),!0),window.addEventListener("pushState",(t=>e(t)),!0)},l=e=>{window.addEventListener("popstate",(t=>e(t)),!0)},d=e=>{const t=o((t=>{const n=function(e){if(!e)return;if(["A","INPUT","TEXTAREA","BUTTON"].includes(e.tagName))return console.log("预设的可跟踪元素 ",e),e;if(e.hasAttribute("track-click"))return console.log("带有 track-click 属性的可跟踪元素 ",e),e;const t=e.closest("a, button, [track-click]");return t?(console.log("找到的可跟踪元素 ",t),t):void 0}(t.target);if(!n)return;const r={element_id:n.id,element_content:n.textContent?.trim()||null,element_class_name:n.className||null,element_name:n.getAttribute("name")||null,element_type:n.tagName.toLowerCase(),element_path:a(n),scroll_width:document.documentElement.scrollWidth,scroll_height:document.documentElement.scrollHeight,pageX:t.pageX,pageY:t.pageY,ratio:window.devicePixelRatio||1};e({data_type:"common",event_type:"click",event_detail:r})}));window.addEventListener("click",t,!0)},u=e=>{history.pushState=s("pushState"),history.replaceState=s("replaceState"),(e=>{const t=r((()=>{const{host:t,hostname:n,href:r,protocol:o,origin:a,port:s,pathname:c,search:l,hash:d}=window.location,u={host:t,hostname:n,href:r,protocol:o,origin:a,port:s,pathname:c,search:l,hash:d,title:document.title,language:navigator.language.substring(0,2),user_agent:navigator.userAgent,win_screen:`${window.screen.width}x${window.screen.height}`,doc_screen:`${document.documentElement.clientWidth}x${document.documentElement.clientHeight}`,referrer:document.referrer,type:i()};e({data_type:"common",event_type:"view",event_detail:u})}));n((()=>{t()})),l((()=>{t()})),c((()=>{t()}))})(e),(e=>{let t="",o="",a=0;const i=()=>{if(o&&a){const n=Date.now()-a;n>0&&e({data_type:"common",event_type:"leave",event_detail:{url:o,referrer:t,duration:n}})}a=Date.now(),t=o,o=window.location.href},s=r(i);n((()=>{s()})),l((()=>{s()})),c((()=>{s()})),document.addEventListener("visibilitychange",(()=>{"hidden"===document.visibilityState?i():a=Date.now()})),window.addEventListener("pagehide",(()=>{i()}),!0),window.addEventListener("pageshow",(()=>{a=Date.now()}),!0),window.addEventListener("beforeunload",(()=>{i()}),!0)})(e),d(e)};let m=null,h=null;const p="track-expose",w="track-view",g="TRACK_MONITOR_DATA",f="TRACK_MONITOR_USER";let v=null,y=null;const _=(e,t,n)=>({session_id:t.session_id,request_id:t.request_id,spm:t.spm,data_type:t.data_type||"behavior",event_type:t.event_type,event_detail:"string"==typeof t.event_detail?t.event_detail:JSON.stringify(t.event_detail),page_url:location.href,os:window.navigator.platform,user_id:localStorage.getItem(f)||void 0,user_agent:window.navigator.userAgent,screen_width:window.screen.width,screen_height:window.screen.height,lib_version:"1.0.0",lib_type:"js",event_time:(new Date).getTime().toString(),timezone_offset:(new Date).getTimezoneOffset().toString(),site_id:n,channel_id:e}),E=n=>{const{url:r="http://api.mossego.cn/push/track-data",accessKey:o,cacheNumber:a=50,reportInterval:i=3e4,maxCacheSize:s=56,maxRetries:c=10,log:l}=n,d=r.includes("?")?`${r}&ak=${o}`:`${r}?ak=${o}`;let E=0;const b=e=>{try{localStorage.setItem(f,e)}catch(e){console.error("monitor bind user fail: ",e)}},S=()=>{localStorage.removeItem(f)},T=()=>{let e=[];try{e=JSON.parse(localStorage.getItem(g)||"[]")}catch(e){console.error("get monitor data fail: ",e)}return e},A=e=>{try{localStorage.setItem(g,JSON.stringify(e))}catch(t){console.error("save monitor data error: ",e),localStorage.setItem(g,"[]")}},$=t=>{if(l&&console.log("report data ",t),e){const e=((e,t)=>navigator.sendBeacon(e,t))(d,new Blob([JSON.stringify(t)],{type:"application/json"}));if(e)return Promise.resolve()}return(({method:e,url:t,data:n,headers:r={},withCredentials:o=!1})=>new Promise(((a,i)=>{const s=new XMLHttpRequest;s.open(e,t,!0),Object.keys(r).forEach((e=>{s.setRequestHeader(e,r[e])})),s.withCredentials=!!o,s.onreadystatechange=()=>{if(4===s.readyState)if(s.status>=200&&s.status<300)try{const e=JSON.parse(s.responseText);a(e)}catch(e){i(e)}else i(new Error(`Request failed with status ${s.status}`))},s.onerror=()=>{i(new Error("Network error"))},s.ontimeout=()=>{i(new Error("Request timeout"))},n?s.send(n):s.send()})))({url:d,method:"POST",headers:{"Content-Type":"application/json"},data:JSON.stringify(t),withCredentials:!0})},L=(e=!1)=>{const t=T();if(t.length){if(!E&&(t.length>=a||e))return v&&(clearTimeout(v),v=null),A([]),void $(t).then((()=>{E=0})).catch((()=>{console.error("send report fail"),N(t)})).finally((()=>{L()}));if(null===v){v=setTimeout((()=>{const e=T();e.length?y||(A([]),y=$(e).then((()=>{E=0})).catch((()=>{N(e)})).finally((()=>{v=null,y=null,L()}))):v=null}),(E+1)*i)}}},N=e=>{if(E++,E>c)return void(E=0);const t=T();A(t.concat(e))},k=e=>{$([_(n.channelId,e,n.siteId)])},I=e=>{let t=[_(n.channelId,e,n.siteId)];try{const e=T();e&&(t=t.concat(e),A(t));const n=(e=>{const t=JSON.stringify(e);return(new TextEncoder).encode(t).length/1024})(t);L(n>=s)}catch(e){console.error("lazy report fail: ",e)}};return t((()=>{L(!0)})),(e=>{try{h=new WeakMap}catch(e){console.error("create WeakMap fail: ",e)}const t=e=>{if(h?.get(e))return;const t=e.dataset.duration||500;h?.set(e,{duration:t,timer:null}),m?.observe(e)},n=e=>{const t=h?.get(e);t&&(m?.unobserve(e),t?.timer&&clearTimeout(t.timer),h?.delete(e))},r=t=>{t.forEach((t=>{const r=t.target,o=h?.get(r);o&&(o.timer&&(clearTimeout(o.timer),o.timer=null),t.isIntersecting&&(o.timer=window.setTimeout((()=>{const t=r.dataset.eventDetail;r.hasAttribute(w)&&(e({spm:r.dataset.spm,data_type:"behavior",event_type:"view",event_detail:t}),r.removeAttribute(w)),r.hasAttribute(p)?e({spm:r.dataset.spm,data_type:"behavior",event_type:"expose",event_detail:t}):n(r)}),o.duration)))}))};m=(()=>{let e=null;try{e=new IntersectionObserver(r,{root:null,threshold:.5})}catch(e){console.error("create IntersectionObserver fail: ",e)}return e})(),document.querySelectorAll(`[${p}]`).forEach((e=>t(e))),document.querySelectorAll(`[${w}]`).forEach((e=>t(e))),new MutationObserver((e=>{e.forEach((e=>{if("attributes"===e.type&&e.target instanceof HTMLElement){const r=e.target;r.hasAttribute(p)||r.hasAttribute(w)?t(r):n(r)}else"childList"===e.type&&(e.addedNodes.forEach((e=>{e instanceof HTMLElement&&(e.hasAttribute(p)||e.hasAttribute(w)?t(e):(e.querySelectorAll(`[${p}]`).forEach((e=>t(e))),e.querySelectorAll(`[${w}]`).forEach((e=>t(e)))))})),e.removedNodes.forEach((e=>{if(e instanceof HTMLElement){const t=h?.get(e);t?n(e):(e.querySelectorAll(`[${p}]`).forEach((e=>n(e))),e.querySelectorAll(`[${w}]`).forEach((e=>n(e))))}})))}))})).observe(document.body,{attributes:!0,childList:!0,subtree:!0})})(I),!1!==n.behavior&&u(I),window.addEventListener("beforeunload",(()=>{L(!0)})),document.addEventListener("visibilitychange",(()=>{"hidden"===document.visibilityState&&L(!0)})),window.addEventListener("pagehide",(()=>{L(!0)})),window.trackMonitor={report:I,realtimeReport:k,bindUser:b,unBindUser:S},{bindUser:b,unBindUser:S,report:I,realtimeReport:k}};export{E as default};
@@ -0,0 +1,9 @@
1
+ export interface AnyObject {
2
+ [key: string]: any;
3
+ }
4
+ export interface ReportBaseData {
5
+ }
6
+ export interface ClientReportData {
7
+ }
8
+ export interface ReportData extends ReportBaseData, Omit<ClientReportData, 'event_detail'> {
9
+ }
@@ -0,0 +1,39 @@
1
+ export type DataType = "common" | "behavior" | "performance" | "error" | "custom";
2
+ export type EventType = "click" | "expose" | "view" | "cart" | "buy" | "pay" | "leave";
3
+ export interface BaseOptions {
4
+ url?: string;
5
+ accessKey: string;
6
+ cacheNumber?: number;
7
+ reportInterval?: number;
8
+ maxCacheSize?: number;
9
+ maxRetries?: number;
10
+ siteId?: string;
11
+ channelId: string;
12
+ log?: boolean;
13
+ behavior?: boolean;
14
+ }
15
+ export interface SdkReportData {
16
+ page_url: string;
17
+ os: string;
18
+ user_id?: string;
19
+ user_agent: string;
20
+ screen_width: number;
21
+ screen_height: number;
22
+ lib_version: string;
23
+ lib_type: string;
24
+ event_time: string;
25
+ timezone_offset: string;
26
+ }
27
+ export interface ClientReportData {
28
+ session_id?: string;
29
+ request_id?: string;
30
+ spm?: string;
31
+ data_type?: DataType | string;
32
+ event_type: EventType | string;
33
+ event_detail: any;
34
+ site_id?: string;
35
+ channel_id?: string;
36
+ }
37
+ export interface ReportData extends SdkReportData, ClientReportData {
38
+ }
39
+ export type ReportFunction = (data: ClientReportData) => void;
@@ -0,0 +1,3 @@
1
+ import { ReportFunction } from "@/interface";
2
+ export declare const behaviorPlugin: (report: ReportFunction) => void;
3
+ export default behaviorPlugin;
@@ -0,0 +1,3 @@
1
+ import { ReportFunction } from "../interface";
2
+ export declare const exposePlugin: (lazyReport: ReportFunction) => void;
3
+ export default exposePlugin;
@@ -0,0 +1,2 @@
1
+ export * from './behavior';
2
+ export * from './expose';
@@ -0,0 +1,36 @@
1
+ export declare const nextTick: ((callback: IdleRequestCallback, options?: IdleRequestOptions | undefined) => number) & typeof requestIdleCallback;
2
+ /**
3
+ * 在页面完全加载后执行回调函数
4
+ *
5
+ * 该函数会检查文档的 readyState 状态:
6
+ * 1. 如果页面已经加载完成(readyState === 'complete'),则通过 setTimeout 异步执行回调
7
+ * 2. 如果页面仍在加载中,则添加一个一次性监听器,监听 'pageshow' 事件(页面完全加载并显示时触发)
8
+ *
9
+ * @param {Function} callback - 页面加载完成后要执行的回调函数
10
+ *
11
+ * 使用示例:
12
+ * afterLoad(() => {
13
+ * console.log('页面已完全加载');
14
+ * });
15
+ */
16
+ export declare const afterLoad: (callback: any) => void;
17
+ /**
18
+ * 防抖函数
19
+ * @param func 需要防抖的函数
20
+ * @param delay 等待时间(毫秒)
21
+ * @param immediate 是否立即执行
22
+ * @returns 防抖后的函数
23
+ */
24
+ export declare function debounce<T extends (...args: any[]) => any>(func: T, delay?: number, immediate?: boolean): (...args: Parameters<T>) => void;
25
+ /**
26
+ * 函数节流
27
+ *
28
+ * @export
29
+ * @param {Function} fn 需要节流的函数
30
+ * @param {number} delay 节流的时间间隔(毫秒)
31
+ * @return {(...args: any[]) => void} 返回一个包含节流功能的函数
32
+ */
33
+ export declare const throttle: <T extends (...args: any[]) => void>(fn: T, delay?: number) => (...args: Parameters<T>) => void;
34
+ export declare function isTrackElement(element: HTMLElement): Element | undefined;
35
+ export declare function getNodeXPath(node: Element, curPath?: string): string;
36
+ export declare const getNavigationType: () => number | string;
@@ -0,0 +1,3 @@
1
+ export declare const wrHistory: () => void;
2
+ export declare const proxyHistory: (handler: Function) => void;
3
+ export declare const proxyHash: (handler: Function) => void;
@@ -0,0 +1,24 @@
1
+ type RequestMethod = "GET" | "POST";
2
+ interface AjaxOptions {
3
+ method: RequestMethod;
4
+ url: string;
5
+ data?: any;
6
+ headers?: Record<string, string>;
7
+ withCredentials?: boolean;
8
+ }
9
+ /**
10
+ * 是否支持sendBeacon
11
+ */
12
+ export declare const isSupportBeacon = true;
13
+ /**
14
+ * ajax请求
15
+ * @param {AjaxOptions} params 请求配置
16
+ */
17
+ export declare const ajax: <T>({ method, url, data, headers, withCredentials, }: AjaxOptions) => Promise<T>;
18
+ /**
19
+ * sendBeacon请求
20
+ * @param {string} url 接口地址
21
+ * @param {BodyInit | null} data 请求数据
22
+ */
23
+ export declare const sendBeacon: (url: string, data?: BodyInit | null) => boolean;
24
+ export {};
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@tengence/track-sdk-web",
3
+ "version": "1.0.0",
4
+ "description": "tengence web client track sdk",
5
+ "main": "dist/index.mjs",
6
+ "module": "dist/index.mjs",
7
+ "exports": {
8
+ ".": {
9
+ "import": {
10
+ "types": "./dist/index.d.ts",
11
+ "default": "./dist/index.mjs"
12
+ }
13
+ }
14
+ },
15
+ "scripts": {
16
+ "build": "rollup --c rollup.config.ts --configPlugin typescript",
17
+ "release": "npm publish --access public"
18
+ },
19
+ "private": false,
20
+ "author": "tengence",
21
+ "license": "ISC",
22
+ "devDependencies": {
23
+ "@rollup/plugin-alias": "^5.1.1",
24
+ "@rollup/plugin-commonjs": "^28.0.1",
25
+ "@rollup/plugin-json": "^6.1.0",
26
+ "@rollup/plugin-node-resolve": "^15.3.0",
27
+ "@rollup/plugin-terser": "^0.4.4",
28
+ "@rollup/plugin-typescript": "^12.1.1",
29
+ "@types/node": "^22.13.1",
30
+ "cors": "^2.8.5",
31
+ "express": "^4.21.2",
32
+ "rollup": "^4.34.6",
33
+ "tslib": "^2.8.0",
34
+ "typescript": "^4.9.5"
35
+ },
36
+ "files": [
37
+ "dist"
38
+ ]
39
+ }