diff --git a/package.json b/package.json index 224f1999fc7dffd94cbbdad0d7fb8729b1ba6186..18373b5c1d4ea44fd920c0c448ee6c3e7c9b31e5 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,8 @@ "description": "", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", - "dev": "pnpm --filter demo dev" + "dev:demo": "pnpm --filter demo dev", + "dev": "pnpm --filter @opensig/open-analytics dev" }, "keywords": [], "author": "", diff --git a/packages/analytics/README.md b/packages/analytics/README.md index 429483859143ffaac20069f4ad52cb0a9c643a64..f56ba5c18f9b02aa3f7058cf4e0c5db493475e05 100644 --- a/packages/analytics/README.md +++ b/packages/analytics/README.md @@ -28,6 +28,11 @@ 1. 内置事件字段添加前缀"$" 2. cid,sid 使用 uuid 生成 +# 0.0.7 + +1. 支持服务端执行,解决 SSR\SSG 构建报错 +2. 移除 enable 标识记忆功能,通过调用者控制 enableReporting 确认是否上报 + # TODO 1. 支持在 html 引入; diff --git a/packages/analytics/package.json b/packages/analytics/package.json index ad77b2d6eb6ea9e450f6fc10731bc58b4f449a69..f737215d62843add63069813dc5585affa8dc202 100644 --- a/packages/analytics/package.json +++ b/packages/analytics/package.json @@ -1,6 +1,6 @@ { "name": "@opensig/open-analytics", - "version": "0.0.6", + "version": "0.0.7", "description": "opendesign analytics", "main": "dist/open-analytics.mjs", "types": "dist/open-analytics.d.ts", diff --git a/packages/analytics/src/constant.ts b/packages/analytics/src/constant.ts index 13d198a6a7db58553fd3df213cda7a2ece3cc945..9006f23e72ee69769277084bb33a464a9119db0f 100644 --- a/packages/analytics/src/constant.ts +++ b/packages/analytics/src/constant.ts @@ -1,7 +1,5 @@ export const Constant = { OA_PREFIX: 'oa', // 本地存储key的前缀 - OA_ENABLED: '1', // 开启采集标识 - OA_DISABLED: '0', // 关闭采集标识 SESSION_EXPIRE_TIME: 30 * 60 * 1000, // 会话标识有效期 CLIENT_EXPIRE_TIME: 180 * 24 * 60 * 60 * 1000, // 设备标识有效期 DEFAULT_REQUEST_INTERVAL: 5 * 1000, // 默认上报间隔 diff --git a/packages/analytics/src/open-analytics.ts b/packages/analytics/src/open-analytics.ts index 07cbd08f0a61e69f538666f577021298ed590270..8054a29cf30089272bc4126e8a85986ede2166da 100644 --- a/packages/analytics/src/open-analytics.ts +++ b/packages/analytics/src/open-analytics.ts @@ -1,5 +1,5 @@ import { Storage } from './storage'; -import { whenDocumentReady, isFunction, isPromise, uniqueId } from './utils'; +import { whenDocumentReady, isFunction, isPromise, uniqueId, isClient } from './utils'; import { Constant } from './constant'; import { getInnerEventData, isInnerEvent, CollectorOptions } from './events'; import packageJson from '../package.json'; @@ -9,14 +9,12 @@ 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) { @@ -24,68 +22,29 @@ class AnalyticsStoreKey { } } type StoreKeyIns = InstanceType; - -let store: InstanceType; -/** - * 初始化存储器 - * @param appId {string} - */ -function initStorage(target?: globalThis.Storage) { - store = new Storage(target || globalThis.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(), - }), - setOption: { - expire: Date.now() + Constant.CLIENT_EXPIRE_TIME, - }, - onValid() { - store.setExpire(aKey, Date.now() + Constant.CLIENT_EXPIRE_TIME); - }, - }).value; - +function createStorageTarget() { + if (isClient) { + return globalThis.localStorage; + } + const store: Record = {}; 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(), - }), - setOption: { - expire: Date.now() + Constant.SESSION_EXPIRE_TIME, + setItem(key: string, value: string) { + store[key] = value; }, - onValid() { - store.setExpire(sKey, Date.now() + Constant.SESSION_EXPIRE_TIME); + getItem(key: string) { + return store[key]; }, - }).value; - - return session.id; + removeItem(key: string) { + delete store[key]; + }, + }; } export class OpenAnalytics { - enabled: boolean; - + #store: InstanceType; #timer: number | null; #firstReport: boolean; #request: ReportRequest; @@ -100,12 +59,14 @@ export class OpenAnalytics { // 上报间隔,默认3s #requestInterval: number; #maxEvents: number; + + enabled: boolean; /** * 构造函数 * @param params {OpenAnalyticsParams} */ constructor(params: OpenAnalyticsParams) { - initStorage(); + this.#store = new Storage(createStorageTarget()); this.#request = params.request; this.#immediate = params.immediate ?? false; this.#appKey = params.appKey ?? ''; @@ -116,19 +77,61 @@ export class OpenAnalytics { this.#firstReport = true; - this.enabled = store.get(this.#StoreKey.enabled).value === Constant.OA_ENABLED; + this.#eventData = this.#store.getAlways(this.#StoreKey.events, { + defaultValue: () => [], + }).value; - 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); - } + this.#header = {}; + + this.enabled = false; + } + /** + * 初始化通用数据 + * @param keys {string} + * @param appId {string} + */ + #initHeader(keys: StoreKeyIns, appId: string): EventHeader { + const aKey = keys.client; + const client = this.#store.getAlways(aKey, { + defaultValue: () => ({ + id: uniqueId(), + }), + setOption: { + expire: Date.now() + Constant.CLIENT_EXPIRE_TIME, + }, + onValid: () => { + this.#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 + */ + #getSessionId(sKey: string) { + const session = this.#store.getAlways(sKey, { + defaultValue: () => ({ + id: uniqueId(), + }), + setOption: { + expire: Date.now() + Constant.SESSION_EXPIRE_TIME, + }, + onValid: () => { + this.#store.setExpire(sKey, Date.now() + Constant.SESSION_EXPIRE_TIME); + }, + }).value; + + return session.id; } /** * 搜集数据 @@ -141,9 +144,60 @@ export class OpenAnalytics { this.#eventData.shift(); } if (this.enabled) { - store.set(this.#StoreKey.events, this.#eventData); + this.#store.set(this.#StoreKey.events, this.#eventData); - this.runRequestPlan(immediate); + 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 = []; + this.#store.set(this.#StoreKey.events, []); + } + }); + } else { + this.#eventData = []; + this.#store.set(this.#StoreKey.events, []); } } /** @@ -162,10 +216,9 @@ export class OpenAnalytics { } if (this.enabled) { - store.set(this.#StoreKey.enabled, Constant.OA_ENABLED); - this.#header = Object.assign(initHeader(this.#StoreKey, this.#appKey), this.#header); + this.#header = Object.assign(this.#initHeader(this.#StoreKey, this.#appKey), this.#header); // 初始化sessionId - this.#sessionId = getSessionId(this.#StoreKey.session); + this.#sessionId = this.#getSessionId(this.#StoreKey.session); // 给内存中事件添加sessionId this.#eventData.forEach((event) => { if (event.sId === '') { @@ -173,17 +226,16 @@ export class OpenAnalytics { } }); // 将数据存储到本地 - store.set(this.#StoreKey.events, this.#eventData); + this.#store.set(this.#StoreKey.events, this.#eventData); // 执行上报策略 - this.runRequestPlan(); + 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); + this.#store.remove(this.#StoreKey.events); + this.#store.remove(this.#StoreKey.client); + this.#store.remove(this.#StoreKey.session); } } /** @@ -209,59 +261,9 @@ export class OpenAnalytics { event: event, time: Date.now(), properties: reportData, - sId: this.enabled ? getSessionId(this.#StoreKey.session) : '', + sId: this.enabled ? this.#getSessionId(this.#StoreKey.session) : '', }; this.#collect(eventData, 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, []); - } - } } diff --git a/packages/analytics/src/storage.ts b/packages/analytics/src/storage.ts index b58abe216a0480a6acf7f3f98a1c80b5e3e590b5..3fdb9be080fa69334c2ad5667ace7b3688ca22b3 100644 --- a/packages/analytics/src/storage.ts +++ b/packages/analytics/src/storage.ts @@ -8,10 +8,17 @@ interface StorageSetOptions { interface StorageOptions { checkExpiration?: (time: number) => boolean; } + +export interface StorageTarget { + setItem: (key: string, value: any) => void; + removeItem: (key: string) => void; + getItem: (key: string) => string | null; +} + export class Storage { - store: globalThis.Storage = globalThis.localStorage; + store: StorageTarget; checkExpiration: (expire: number) => boolean; - constructor(target: globalThis.Storage, options?: StorageOptions) { + constructor(target: StorageTarget, options?: StorageOptions) { this.store = target; this.checkExpiration = isFunction(options?.checkExpiration) ? options?.checkExpiration diff --git a/packages/analytics/src/utils.ts b/packages/analytics/src/utils.ts index a86fd821ccd6885e898d7953d604e63f5b6fabca..17d92462fd3a3edae1cd6f42dddaed5149951e96 100644 --- a/packages/analytics/src/utils.ts +++ b/packages/analytics/src/utils.ts @@ -47,3 +47,8 @@ export function whenWindowLoad(callback: () => any): void { callback(); } } + +/** + * 是否是浏览器环境 + */ +export const isClient = typeof window !== 'undefined'; diff --git a/packages/analytics/test/main.ts b/packages/analytics/test/main.ts index 2655088089b9383a94cd0a7bbc020bbd27a92397..7938f63e1d232e28d3d4851096a127ab34843cc7 100644 --- a/packages/analytics/test/main.ts +++ b/packages/analytics/test/main.ts @@ -23,9 +23,8 @@ function enabledOA(enabled) { oa.enableReporting(enabled); localStorage.setItem('enabled', enabled ? '1' : '0'); } -if (localStorage.getItem('enabled') !== '1') { - enabledOA(false); -} + +enabledOA(localStorage.getItem('enabled') === '1'); oa.report(OpenEventKeys.PV, () => ({ id: 'home',