From bb2c9575be36e6b41d52840ae58cb250517a1b87 Mon Sep 17 00:00:00 2001 From: devin-cwd Date: Wed, 7 Aug 2024 09:28:55 +0800 Subject: [PATCH 1/5] =?UTF-8?q?refactor:=20=E6=95=B4=E6=94=B9index.ts?= =?UTF-8?q?=E4=B8=BA=E7=BB=9F=E4=B8=80=E5=AF=BC=E5=87=BA=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/analytics/src/index.ts | 265 +---------------------- packages/analytics/src/open-analytics.ts | 255 ++++++++++++++++++++++ packages/analytics/src/plugins/client.ts | 25 +++ packages/analytics/src/utils.ts | 8 - 4 files changed, 281 insertions(+), 272 deletions(-) create mode 100644 packages/analytics/src/open-analytics.ts create mode 100644 packages/analytics/src/plugins/client.ts diff --git a/packages/analytics/src/index.ts b/packages/analytics/src/index.ts index f027aa8..4b3177b 100644 --- a/packages/analytics/src/index.ts +++ b/packages/analytics/src/index.ts @@ -1,265 +1,2 @@ -import { Storage } from './storage'; -import { whenDocumentReady, getClientByUA, isFunction, isPromise, uniqueId } from './utils'; -import { Constant } from './constant'; -import { getInnerEventData, isInnerEvent, CollectorOptions } from './events'; -import packageJson from '../package.json'; -import { EventContent, EventData, EventHeader, OpenAnalyticsParams, ReportRequest } from './types'; - +export * from './open-analytics'; export { OpenEventKeys } from './events/_keys'; - -class AnalyticsStoreKey { - appPrefix: string; - client: string; - session: string; - enabled: string; - events: string; - constructor(appKey: string = '') { - this.appPrefix = appKey ? `${appKey}-` : ''; - - this.client = this.getKey('client'); - this.session = this.getKey('session'); - this.enabled = this.getKey('enabled'); - this.events = this.getKey('events'); - } - getKey(key: string) { - return `${Constant.OA_PREFIX}-${this.appPrefix}${key}`; - } -} -type StoreKeyIns = InstanceType; - -const store = new Storage(localStorage); - -/** - * 初始化通用数据 - * @param keys {string} - * @param appId {string} - */ -function initHeader(keys: StoreKeyIns, appId: string): EventHeader { - const aKey = keys.client; - const client = store.getAlways(aKey, { - defaultValue: () => ({ - id: uniqueId('', Constant.ID_LENGTH), - }), - setOption: { - expire: Date.now() + Constant.CLIENT_EXPIRE_TIME, - }, - onValid() { - store.setExpire(aKey, Date.now() + Constant.CLIENT_EXPIRE_TIME); - }, - }).value; - - const { browser, os, device } = getClientByUA(); - return { - cId: client.id, - aId: appId, - oa_version: packageJson.version, - viewport_width: window.innerWidth, - viewport_height: window.innerHeight, - screen_width: window.screen.width || window.innerWidth, - screen_height: window.screen.height || window.innerHeight, - os: os.name ?? '', - os_version: os.version ?? '', - browser: browser.name ?? '', - browser_version: browser.version ?? '', - device: device.model ?? '', - device_type: device.type ?? '', - device_vendor: device.vendor ?? '', - }; -} -/** - * 获取会话id,每次获取,延长有效期 - * @param sKey session key - */ -function getSessionId(sKey: string) { - const session = store.getAlways(sKey, { - defaultValue: () => ({ - id: uniqueId('', Constant.ID_LENGTH), - }), - setOption: { - expire: Date.now() + Constant.SESSION_EXPIRE_TIME, - }, - onValid() { - store.setExpire(sKey, Date.now() + Constant.SESSION_EXPIRE_TIME); - }, - }).value; - - return session.id; -} - -export class OpenAnalytics { - request: ReportRequest; - eventData: EventData[]; - immediate: boolean; - sessionId: string = ''; - appKey: string = ''; - header: EventHeader; - enabled: boolean; - StoreKey: StoreKeyIns; - // 自定义上报策略 - requestPlan?: (requestFn: () => void) => void; - // 上报间隔,默认3s - requestInterval: number; - maxEvents: number; - - #timer: number | null; - #firstReport: boolean; - /** - * 构造函数 - * @param params {OpenAnalyticsParams} - */ - constructor(params: OpenAnalyticsParams) { - this.request = params.request; - this.immediate = params.immediate ?? false; - this.appKey = params.appKey ?? ''; - this.StoreKey = new AnalyticsStoreKey(params.appKey); - this.requestInterval = params.requestInterval ?? Constant.DEFAULT_REQUEST_INTERVAL; - this.#timer = null; - this.maxEvents = params.maxEvents ?? Constant.MAX_EVENTS; - - this.#firstReport = true; - - this.enabled = store.get(this.StoreKey.enabled).value === Constant.OA_ENABLED; - - if (this.enabled) { - store.set(this.StoreKey.enabled, Constant.OA_ENABLED); - this.eventData = store.getAlways(this.StoreKey.events, { - defaultValue: () => [], - }).value; - this.header = initHeader(this.StoreKey, this.appKey); - } else { - this.header = {}; - this.eventData = []; - store.remove(this.StoreKey.events); - } - } - /** - * 设置header - */ - setHeader(header: Record) { - Object.assign(this.header, header); - } - /** - * 控制是否发送数据上报 - * @param enabled - */ - enableReporting(enabled: boolean = true) { - if (this.enabled !== enabled) { - this.enabled = enabled; - } - - if (this.enabled) { - store.set(this.StoreKey.enabled, Constant.OA_ENABLED); - this.header = Object.assign(initHeader(this.StoreKey, this.appKey), this.header); - // 初始化sessionId - this.sessionId = getSessionId(this.StoreKey.session); - // 给内存中事件添加sessionId - this.eventData.forEach((event) => { - if (event.sId === '') { - event.sId = this.sessionId; - } - }); - // 将数据存储到本地 - store.set(this.StoreKey.events, this.eventData); - // 执行上报策略 - this.runRequestPlan(); - } else if (this.#timer) { - clearTimeout(this.#timer); - this.#timer = 0; - this.eventData = []; - store.remove(this.StoreKey.enabled); - store.remove(this.StoreKey.events); - store.remove(this.StoreKey.client); - store.remove(this.StoreKey.session); - } - } - /** - * 搜集数据 - */ - collect(data: EventData, immediate?: boolean) { - this.eventData.push(data); - - // 如果事件数超过最大数量,丢弃之前的事件 - if (this.eventData.length > this.maxEvents) { - this.eventData.shift(); - } - if (this.enabled) { - store.set(this.StoreKey.events, this.eventData); - - this.runRequestPlan(immediate); - } - } - /** - * 执行上报策略 - * @param immediate - */ - runRequestPlan(immediate?: boolean) { - if (immediate || this.immediate) { - this.doSendEventData(); - } else if (this.#firstReport) { - this.#firstReport = false; - whenDocumentReady(() => this.doSendEventData()); - } else { - if (isFunction(this.requestPlan)) { - this.requestPlan(this.doSendEventData); - } else { - const run = () => { - this.#timer = window.setTimeout(() => { - this.doSendEventData(); - run(); - }, this.requestInterval); - }; - if (!this.#timer) { - run(); - } - } - } - } - /** - * 发起数据上报 - */ - doSendEventData() { - if (!this.request || !this.enabled || this.eventData.length === 0) { - return; - } - const reportData = { - header: this.header, - body: this.eventData, - }; - const rlt = this.request(reportData); - if (isPromise(rlt)) { - rlt.then((isSuccess) => { - if (isSuccess) { - this.eventData = []; - store.set(this.StoreKey.events, []); - } - }); - } else { - this.eventData = []; - store.set(this.StoreKey.events, []); - } - } - /** - * 采集数据 - * @param event 事件名 - * @param data 事件数据,如果是内部事件,则会在内部事件触发时执行 - * @param options 配置 - */ - async report(event: string, content?: (...opts: any[]) => Promise | EventContent, collectOptions?: CollectorOptions, immediate?: boolean) { - let reportData: EventContent = {}; - - if (isInnerEvent(event)) { - reportData = (await getInnerEventData(event, collectOptions)) || {}; - } else if (content) { - reportData = await content(); - } - - const eventData: EventData = { - event: event, - time: Date.now(), - data: reportData, - sId: this.enabled ? getSessionId(this.StoreKey.session) : '', - }; - - this.collect(eventData, immediate); - } -} diff --git a/packages/analytics/src/open-analytics.ts b/packages/analytics/src/open-analytics.ts new file mode 100644 index 0000000..600545b --- /dev/null +++ b/packages/analytics/src/open-analytics.ts @@ -0,0 +1,255 @@ +import { Storage } from './storage'; +import { whenDocumentReady, isFunction, isPromise, uniqueId } from './utils'; +import { Constant } from './constant'; +import { getInnerEventData, isInnerEvent, CollectorOptions } from './events'; +import packageJson from '../package.json'; +import { EventContent, EventData, EventHeader, OpenAnalyticsParams, ReportRequest } from './types'; + +class AnalyticsStoreKey { + appPrefix: string; + client: string; + session: string; + enabled: string; + events: string; + constructor(appKey: string = '') { + this.appPrefix = appKey ? `${appKey}-` : ''; + + this.client = this.getKey('client'); + this.session = this.getKey('session'); + this.enabled = this.getKey('enabled'); + this.events = this.getKey('events'); + } + getKey(key: string) { + return `${Constant.OA_PREFIX}-${this.appPrefix}${key}`; + } +} +type StoreKeyIns = InstanceType; + +const store = new Storage(localStorage); + +/** + * 初始化通用数据 + * @param keys {string} + * @param appId {string} + */ +function initHeader(keys: StoreKeyIns, appId: string): EventHeader { + const aKey = keys.client; + const client = store.getAlways(aKey, { + defaultValue: () => ({ + id: uniqueId('', Constant.ID_LENGTH), + }), + setOption: { + expire: Date.now() + Constant.CLIENT_EXPIRE_TIME, + }, + onValid() { + store.setExpire(aKey, Date.now() + Constant.CLIENT_EXPIRE_TIME); + }, + }).value; + + return { + cId: client.id, + aId: appId, + oa_version: packageJson.version, + viewport_width: window.innerWidth, + viewport_height: window.innerHeight, + screen_width: window.screen.width || window.innerWidth, + screen_height: window.screen.height || window.innerHeight, + }; +} +/** + * 获取会话id,每次获取,延长有效期 + * @param sKey session key + */ +function getSessionId(sKey: string) { + const session = store.getAlways(sKey, { + defaultValue: () => ({ + id: uniqueId('', Constant.ID_LENGTH), + }), + setOption: { + expire: Date.now() + Constant.SESSION_EXPIRE_TIME, + }, + onValid() { + store.setExpire(sKey, Date.now() + Constant.SESSION_EXPIRE_TIME); + }, + }).value; + + return session.id; +} + +export class OpenAnalytics { + request: ReportRequest; + eventData: EventData[]; + immediate: boolean; + sessionId: string = ''; + appKey: string = ''; + header: EventHeader; + enabled: boolean; + StoreKey: StoreKeyIns; + // 自定义上报策略 + requestPlan?: (requestFn: () => void) => void; + // 上报间隔,默认3s + requestInterval: number; + maxEvents: number; + + #timer: number | null; + #firstReport: boolean; + /** + * 构造函数 + * @param params {OpenAnalyticsParams} + */ + constructor(params: OpenAnalyticsParams) { + this.request = params.request; + this.immediate = params.immediate ?? false; + this.appKey = params.appKey ?? ''; + this.StoreKey = new AnalyticsStoreKey(params.appKey); + this.requestInterval = params.requestInterval ?? Constant.DEFAULT_REQUEST_INTERVAL; + this.#timer = null; + this.maxEvents = params.maxEvents ?? Constant.MAX_EVENTS; + + this.#firstReport = true; + + this.enabled = store.get(this.StoreKey.enabled).value === Constant.OA_ENABLED; + + if (this.enabled) { + store.set(this.StoreKey.enabled, Constant.OA_ENABLED); + this.eventData = store.getAlways(this.StoreKey.events, { + defaultValue: () => [], + }).value; + this.header = initHeader(this.StoreKey, this.appKey); + } else { + this.header = {}; + this.eventData = []; + store.remove(this.StoreKey.events); + } + } + /** + * 设置header + */ + setHeader(header: Record) { + Object.assign(this.header, header); + } + /** + * 控制是否发送数据上报 + * @param enabled + */ + enableReporting(enabled: boolean = true) { + if (this.enabled !== enabled) { + this.enabled = enabled; + } + + if (this.enabled) { + store.set(this.StoreKey.enabled, Constant.OA_ENABLED); + this.header = Object.assign(initHeader(this.StoreKey, this.appKey), this.header); + // 初始化sessionId + this.sessionId = getSessionId(this.StoreKey.session); + // 给内存中事件添加sessionId + this.eventData.forEach((event) => { + if (event.sId === '') { + event.sId = this.sessionId; + } + }); + // 将数据存储到本地 + store.set(this.StoreKey.events, this.eventData); + // 执行上报策略 + this.runRequestPlan(); + } else if (this.#timer) { + clearTimeout(this.#timer); + this.#timer = 0; + this.eventData = []; + store.remove(this.StoreKey.enabled); + store.remove(this.StoreKey.events); + store.remove(this.StoreKey.client); + store.remove(this.StoreKey.session); + } + } + /** + * 搜集数据 + */ + collect(data: EventData, immediate?: boolean) { + this.eventData.push(data); + + // 如果事件数超过最大数量,丢弃之前的事件 + if (this.eventData.length > this.maxEvents) { + this.eventData.shift(); + } + if (this.enabled) { + store.set(this.StoreKey.events, this.eventData); + + this.runRequestPlan(immediate); + } + } + /** + * 执行上报策略 + * @param immediate + */ + runRequestPlan(immediate?: boolean) { + if (immediate || this.immediate) { + this.doSendEventData(); + } else if (this.#firstReport) { + this.#firstReport = false; + whenDocumentReady(() => this.doSendEventData()); + } else { + if (isFunction(this.requestPlan)) { + this.requestPlan(this.doSendEventData); + } else { + const run = () => { + this.#timer = window.setTimeout(() => { + this.doSendEventData(); + run(); + }, this.requestInterval); + }; + if (!this.#timer) { + run(); + } + } + } + } + /** + * 发起数据上报 + */ + doSendEventData() { + if (!this.request || !this.enabled || this.eventData.length === 0) { + return; + } + const reportData = { + header: this.header, + body: this.eventData, + }; + const rlt = this.request(reportData); + if (isPromise(rlt)) { + rlt.then((isSuccess) => { + if (isSuccess) { + this.eventData = []; + store.set(this.StoreKey.events, []); + } + }); + } else { + this.eventData = []; + store.set(this.StoreKey.events, []); + } + } + /** + * 采集数据 + * @param event 事件名 + * @param data 事件数据,如果是内部事件,则会在内部事件触发时执行 + * @param options 配置 + */ + async report(event: string, content?: (...opts: any[]) => Promise | EventContent, collectOptions?: CollectorOptions, immediate?: boolean) { + let reportData: EventContent = {}; + + if (isInnerEvent(event)) { + reportData = (await getInnerEventData(event, collectOptions)) || {}; + } else if (content) { + reportData = await content(); + } + + const eventData: EventData = { + event: event, + time: Date.now(), + data: reportData, + sId: this.enabled ? getSessionId(this.StoreKey.session) : '', + }; + + this.collect(eventData, immediate); + } +} diff --git a/packages/analytics/src/plugins/client.ts b/packages/analytics/src/plugins/client.ts new file mode 100644 index 0000000..77ffaeb --- /dev/null +++ b/packages/analytics/src/plugins/client.ts @@ -0,0 +1,25 @@ +import { UAParser } from 'ua-parser-js'; + +/** + * 根据userAgent信息获取系统及浏览器信息 + */ +function getClientByUA(userAgent: string = window.navigator.userAgent) { + const { browser, os, device } = UAParser(userAgent); + return { browser, os, device }; +} +/** + * 获取客户端信息:browser, os, device + * @returns + */ +export function getClientInfo() { + const { browser, os, device } = getClientByUA(); + return { + os: os.name ?? '', + os_version: os.version ?? '', + browser: browser.name ?? '', + browser_version: browser.version ?? '', + device: device.model ?? '', + device_type: device.type ?? '', + device_vendor: device.vendor ?? '', + }; +} diff --git a/packages/analytics/src/utils.ts b/packages/analytics/src/utils.ts index 0b7338c..a7ccf5b 100644 --- a/packages/analytics/src/utils.ts +++ b/packages/analytics/src/utils.ts @@ -1,4 +1,3 @@ -import { UAParser } from 'ua-parser-js'; /** * 生成随机字符串 * @param prefix 前缀 @@ -36,13 +35,6 @@ export function isObject(val: unknown): val is Record { export const isPromise = (val: unknown): val is Promise => { return isObject(val) && isFunction(val.then) && isFunction(val.catch); }; -/** - * 根据userAgent信息获取系统及浏览器信息 - */ -export function getClientByUA(userAgent: string = window.navigator.userAgent) { - const { browser, os, device } = UAParser(userAgent); - return { browser, os, device }; -} /** * 在文档准备完成 * @param callback -- Gitee From 5b56486118cc18a95cbab92b81dc89fd1754a865 Mon Sep 17 00:00:00 2001 From: devin-cwd Date: Wed, 7 Aug 2024 14:30:39 +0800 Subject: [PATCH 2/5] =?UTF-8?q?feat:=20=E5=AF=BC=E5=87=BAplugin?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/analytics/src/events/index.ts | 2 -- packages/analytics/src/index.ts | 1 + packages/analytics/src/plugins/index.ts | 1 + 3 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 packages/analytics/src/plugins/index.ts diff --git a/packages/analytics/src/events/index.ts b/packages/analytics/src/events/index.ts index 60fe289..c200c7b 100644 --- a/packages/analytics/src/events/index.ts +++ b/packages/analytics/src/events/index.ts @@ -27,8 +27,6 @@ for (const path in modules) { } } -export { OpenEventKeys, Events }; - export function isInnerEvent(event: string) { return Events.has(event); } diff --git a/packages/analytics/src/index.ts b/packages/analytics/src/index.ts index 4b3177b..7cc18bc 100644 --- a/packages/analytics/src/index.ts +++ b/packages/analytics/src/index.ts @@ -1,2 +1,3 @@ export * from './open-analytics'; export { OpenEventKeys } from './events/_keys'; +export * from './plugins'; diff --git a/packages/analytics/src/plugins/index.ts b/packages/analytics/src/plugins/index.ts new file mode 100644 index 0000000..4f1cce4 --- /dev/null +++ b/packages/analytics/src/plugins/index.ts @@ -0,0 +1 @@ +export * from './client'; -- Gitee From d53528a333a28c062abeaeff3c51aeb495f6bbbc Mon Sep 17 00:00:00 2001 From: devin-cwd Date: Wed, 7 Aug 2024 14:31:00 +0800 Subject: [PATCH 3/5] =?UTF-8?q?demo=EF=BC=9A=20=E4=BC=98=E5=8C=96=E7=A4=BA?= =?UTF-8?q?=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/analytics/test/main.ts | 7 +++-- packages/demo/src/App.vue | 4 +-- packages/demo/src/analytics.ts | 7 +++-- packages/demo/src/components/TheAside.vue | 28 ++++++++---------- packages/demo/src/main.ts | 24 +++++++-------- packages/demo/src/router.ts | 36 +++++++++++------------ packages/demo/vite.config.ts | 13 ++++---- 7 files changed, 59 insertions(+), 60 deletions(-) diff --git a/packages/analytics/test/main.ts b/packages/analytics/test/main.ts index d160fff..de4b7cb 100644 --- a/packages/analytics/test/main.ts +++ b/packages/analytics/test/main.ts @@ -1,4 +1,5 @@ import { OpenAnalytics, OpenEventKeys } from '../src/index'; +import { getClientInfo } from '../src/plugins'; const btn1 = document.querySelector('#btn1'); const btnOpen = document.querySelector('#btn-open'); @@ -15,9 +16,9 @@ const oa = new OpenAnalytics({ }, // immediate: true, }); -oa.setHeader({ - appCode: '123', -}); + +oa.setHeader(getClientInfo()); + function enabledOA(enabled) { oa.enableReporting(enabled); localStorage.setItem('enabled', enabled ? '1' : '0'); diff --git a/packages/demo/src/App.vue b/packages/demo/src/App.vue index f8e06d8..3fb1e71 100644 --- a/packages/demo/src/App.vue +++ b/packages/demo/src/App.vue @@ -1,5 +1,5 @@