diff --git a/src/main.ts b/src/main.ts index 452231d29..3fc8fd0cb 100644 --- a/src/main.ts +++ b/src/main.ts @@ -9,7 +9,7 @@ import VueLazyLoad from 'vue-lazyload' import axios from 'axios' import mainMixin from './renderer/utils/mainMixin' import bus from '@/utils/bus' -import { initBaiduTongJi } from './renderer/utils/analytics' +import { initTalkingData } from './renderer/utils/analytics' webFrame.setVisualZoomLevelLimits(1, 1) webFrame.setLayoutZoomLevelLimits(0, 0) @@ -38,4 +38,4 @@ new Vue({ render: h => h(App) }).$mount('#app') -initBaiduTongJi() +initTalkingData() diff --git a/src/main/apis/app/system/index.ts b/src/main/apis/app/system/index.ts index 329e42343..262d34892 100644 --- a/src/main/apis/app/system/index.ts +++ b/src/main/apis/app/system/index.ts @@ -16,6 +16,7 @@ import picgo from '@core/picgo' import pasteTemplate from '#/utils/pasteTemplate' import pkg from 'root/package.json' import { handleCopyUrl } from '~/main/utils/common' +import { privacyManager } from '~/main/utils/privacyManager' let contextMenu: Menu | null let menu: Menu | null let tray: Tray | null @@ -76,6 +77,12 @@ export function createContextMenu () { db.set('settings.showUpdateTip', !value) } }, + { + label: '隐私协议', + click () { + privacyManager.show(false) + } + }, { label: '重启应用', click () { diff --git a/src/main/apis/app/uploader/index.ts b/src/main/apis/app/uploader/index.ts index 8b34f096e..9beb95c2e 100644 --- a/src/main/apis/app/uploader/index.ts +++ b/src/main/apis/app/uploader/index.ts @@ -11,8 +11,8 @@ import windowManager from 'apis/app/window/windowManager' import { IWindowList } from 'apis/app/window/constants' import util from 'util' import { IPicGo } from 'picgo/dist/src/types' -import { showNotification } from '~/main/utils/common' -import { BAIDU_TONGJI_EVENT } from '~/universal/events/constants' +import { showNotification, calcDurationRange } from '~/main/utils/common' +import { TALKING_DATA_EVENT } from '~/universal/events/constants' const waitForShow = (webcontent: WebContents) => { return new Promise((resolve, reject) => { @@ -37,14 +37,18 @@ const waitForRename = (window: BrowserWindow, id: number): Promise }) } -const handleBaiduTongJi = (webContents: WebContents, options: IAnalyticsData) => { - const data: IBaiduTongJiOptions = { - category: 'upload', - action: options.fromClipboard ? 'clipboard' : 'files', // 上传剪贴板图片还是选择的文件 - opt_label: options.type, - opt_value: options.duration +const handleTalkingData = (webContents: WebContents, options: IAnalyticsData) => { + const data: ITalkingDataOptions = { + EventId: 'upload', + Label: options.type, + MapKv: { + by: options.fromClipboard ? 'clipboard' : 'files', // 上传剪贴板图片还是选择的文文件 + count: options.count, // 上传的数量 + duration: calcDurationRange(options.duration || 0), // 上传耗时 + type: options.type + } } - webContents.send(BAIDU_TONGJI_EVENT, data) + webContents.send(TALKING_DATA_EVENT, data) } class Uploader { @@ -120,7 +124,7 @@ class Uploader { this.uploading = false if (ctx.output.every((item: ImgInfo) => item.imgUrl)) { if (this.webContents) { - handleBaiduTongJi(this.webContents, { + handleTalkingData(this.webContents, { fromClipboard: !img, type: db.get('picBed.current') || 'smms', count: img ? img.length : 1, diff --git a/src/main/events/ipcList.ts b/src/main/events/ipcList.ts index bfd8a193f..5c35ec0f1 100644 --- a/src/main/events/ipcList.ts +++ b/src/main/events/ipcList.ts @@ -14,10 +14,7 @@ import getPicBeds from '~/main/utils/getPicBeds' import shortKeyHandler from 'apis/app/shortKey/shortKeyHandler' import bus from '@core/bus' import { - TOGGLE_SHORTKEY_MODIFIED_MODE, - BAIDU_TONGJI_INIT, - BAIDU_TONGJI_CODE, - BAIDU_TONGJI_INIT_RES + TOGGLE_SHORTKEY_MODIFIED_MODE } from '#/events/constants' import { uploadClipboardFiles, @@ -25,7 +22,6 @@ import { } from '~/main/apis/app/uploader/apis' import picgoCoreIPC from './picgoCoreIPC' import { handleCopyUrl } from '~/main/utils/common' -import axios from 'axios' export default { listen () { @@ -145,26 +141,6 @@ export default { ipcMain.on('updateServer', () => { server.restart() }) - - ipcMain.on(BAIDU_TONGJI_INIT, (evt: IpcMainEvent) => { - axios.get(`https://hm.baidu.com/hm.js?${BAIDU_TONGJI_CODE}`, { - headers: { - referer: 'https://molunerfinn.com/' - } - }).then(res => { - if (res.status === 200) { - let scriptContent: string = res.data - // thanks to https://github.com/joehecn/electron-baidu-tongji/blob/master/index.js - const source = '(h.c.b.su=h.c.b.u||document.location.href),h.c.b.u=f.protocol+"//"+document.location.host+' - if (scriptContent.includes(source)) { - const target = '(h.c.b.su=h.c.b.u||"https://"+c.dm[0]+a[1]),h.c.b.u="https://"+c.dm[0]+' - const target2 = '"https://"+c.dm[0]+window.location.pathname+window.location.hash' - scriptContent = scriptContent.replace(source, target).replace(/window.location.href/g, target2) - } - evt.sender.send(BAIDU_TONGJI_INIT_RES, scriptContent) - } - }) - }) }, dispose () {} } diff --git a/src/main/lifeCycle/index.ts b/src/main/lifeCycle/index.ts index df2b05df9..95cf39a1b 100644 --- a/src/main/lifeCycle/index.ts +++ b/src/main/lifeCycle/index.ts @@ -2,7 +2,6 @@ import { app, globalShortcut, protocol, - dialog, Notification } from 'electron' import { @@ -31,6 +30,7 @@ import shortKeyHandler from 'apis/app/shortKey/shortKeyHandler' import { getUploadFiles } from '~/main/utils/handleArgv' import db from '#/datastore' import bus from '@core/bus' +import { privacyManager } from '~/main/utils/privacyManager' const isDevelopment = process.env.NODE_ENV !== 'production' class LifeCycle { @@ -54,6 +54,10 @@ class LifeCycle { console.error('Vue Devtools failed to install:', e.toString()) } } + const res = await privacyManager.init() + if (!res) { + return app.quit() + } windowManager.create(IWindowList.TRAY_WINDOW) windowManager.create(IWindowList.SETTING_WINDOW) createTray() diff --git a/src/main/utils/common.ts b/src/main/utils/common.ts index 1eb715187..b141c4bce 100644 --- a/src/main/utils/common.ts +++ b/src/main/utils/common.ts @@ -1,5 +1,5 @@ import db from '#/datastore' -import { clipboard, Notification } from 'electron' +import { clipboard, Notification, dialog } from 'electron' export const handleCopyUrl = (str: string): void => { if (db.get('settings.autoCopy') !== false) { @@ -18,7 +18,8 @@ export const showNotification = (options: IPrivateShowNotificationOption = { }) => { const notification = new Notification({ title: options.title, - body: options.body + body: options.body, + icon: options.icon || undefined }) const handleClick = () => { if (options.clickToCopy) { @@ -31,3 +32,40 @@ export const showNotification = (options: IPrivateShowNotificationOption = { }) notification.show() } + +export const showMessageBox = (options: any) => { + return new Promise(async (resolve, reject) => { + dialog.showMessageBox( + options + ).then((res) => { + resolve({ + result: res.response, + checkboxChecked: res.checkboxChecked + }) + }) + }) +} + +export const calcDurationRange = (duration: number) => { + if (duration < 1000) { + return 500 + } else if (duration < 1500) { + return 1000 + } else if (duration < 3000) { + return 2000 + } else if (duration < 5000) { + return 3000 + } else if (duration < 7000) { + return 5000 + } else if (duration < 10000) { + return 8000 + } else if (duration < 12000) { + return 10000 + } else if (duration < 20000) { + return 15000 + } else if (duration < 30000) { + return 20000 + } + // max range + return 100000 +} diff --git a/src/main/utils/privacyManager.ts b/src/main/utils/privacyManager.ts new file mode 100644 index 000000000..97972bc5f --- /dev/null +++ b/src/main/utils/privacyManager.ts @@ -0,0 +1,61 @@ +import db from '#/datastore' +import { ipcMain } from 'electron' +import { showMessageBox } from '~/main/utils/common' +import { SHOW_PRIVACY_MESSAGE } from '~/universal/events/constants' + +class PrivacyManager { + async init () { + ipcMain.on(SHOW_PRIVACY_MESSAGE, () => { + this.show(false) + }) + if (db.get('settings.privacyEnsure') !== true) { + const res = await this.show(true) + // cancel + if (res.result === 1) { + return false + } else { + db.set('settings.privacyEnsure', true) + return true + } + } + return true + } + + async show (showCancel = true) { + const res = await showMessageBox({ + type: 'info', + buttons: showCancel ? ['Yes', 'No'] : ['Yes'], + title: '隐私协议', + message: ` + 本软件尊重并保护所有使用服务用户的个人隐私权。为了给您提供更准确、更优质的服务,本软件会按照本隐私权政策的规定使用和收集您的一些行为信息。您在同意本软件服务使用协议之时,即视为您已经同意本隐私权政策全部内容。本隐私权政策属于本软件服务使用协议不可分割的一部分,如果不同意将无法使用。本协议会定期更新。 + + 1.适用范围 + + a)在您使用本软件时,本软件会记录的您对本软件的一些操作行为信息,包括但不限于您使用本软件进行文件上传的耗时、类型、数量等信息。 + + 2.信息的使用 + + a)在获得您的使用数据之后,本软件会将其上传至数据分析服务器,以便分析数据后,提供给您更好的服务。 + + 3.信息披露 + + a)本软件不会将您的信息披露给不受信任的第三方。 + + b)根据法律的有关规定,或者行政或司法机构的要求,向第三方或者行政、司法机构披露; + + c)如您出现违反中国有关法律、法规或者相关规则的情况,需要向第三方披露; + + 4.信息安全 + + a)本软件不会收集您的个人信息、密钥信息等隐私信息,所收集的信息仅仅作为改善软件、优化体验、了解软件日活等用途。 + ` + }) + return res + } +} + +const privacyManager = new PrivacyManager() + +export { + privacyManager +} diff --git a/src/renderer/layouts/Main.vue b/src/renderer/layouts/Main.vue index 3a839d9b9..88ef55fa1 100644 --- a/src/renderer/layouts/Main.vue +++ b/src/renderer/layouts/Main.vue @@ -186,6 +186,9 @@ import { import db from '#/datastore' import mixin from '@/utils/mixin' import InputBoxDialog from '@/components/InputBoxDialog.vue' +import { + SHOW_PRIVACY_MESSAGE +} from '~/universal/events/constants' const { Menu, dialog, BrowserWindow } = remote const customLinkRule = (rule: string, value: string, callback: (arg0?: Error) => void) => { if (!/\$url/.test(value)) { @@ -301,6 +304,12 @@ export default class extends Vue { click () { _this.qrcodeVisible = true } + }, + { + label: '隐私协议', + click () { + ipcRenderer.send(SHOW_PRIVACY_MESSAGE) + } } ] this.menu = Menu.buildFromTemplate(template) diff --git a/src/renderer/pages/MiniPage.vue b/src/renderer/pages/MiniPage.vue index ee3f7bebe..2f8930694 100644 --- a/src/renderer/pages/MiniPage.vue +++ b/src/renderer/pages/MiniPage.vue @@ -23,7 +23,7 @@ import { IpcRendererEvent, remote } from 'electron' -import path from 'path' +import { SHOW_PRIVACY_MESSAGE } from '~/universal/events/constants' @Component({ name: 'mini-page', mixins: [mixin] @@ -176,6 +176,12 @@ export default class extends Vue { remote.BrowserWindow.getFocusedWindow()!.hide() } }, + { + label: '隐私协议', + click () { + ipcRenderer.send(SHOW_PRIVACY_MESSAGE) + } + }, { label: '重启应用', click () { diff --git a/src/renderer/utils/analytics.ts b/src/renderer/utils/analytics.ts index da8e32cec..1c85a4d6e 100644 --- a/src/renderer/utils/analytics.ts +++ b/src/renderer/utils/analytics.ts @@ -1,26 +1,23 @@ /* eslint-disable camelcase */ -import { ipcRenderer } from 'electron' import { - BAIDU_TONGJI_INIT, - BAIDU_TONGJI_INIT_RES, - BAIDU_TONGJI_EVENT + TALKING_DATA_APPID, TALKING_DATA_EVENT } from '~/universal/events/constants' -import { handleBaiduTongJiEvent } from './common' +import pkg from 'root/package.json' +import { ipcRenderer } from 'electron' +import { handleTalkingDataEvent } from './common' +const { version } = pkg -ipcRenderer.on(BAIDU_TONGJI_INIT_RES, (_, scriptContent) => { - window._hmt = window._hmt || [] - const hm = document.createElement('script') - hm.text = scriptContent - const head = document.getElementsByTagName('head')[0] - head.appendChild(hm) -}) +export const initTalkingData = () => { + setTimeout(() => { + const talkingDataScript = document.createElement('script') -ipcRenderer.on(BAIDU_TONGJI_EVENT, (_, data: IBaiduTongJiOptions) => { - handleBaiduTongJiEvent(data) -}) + talkingDataScript.src = `http://sdk.talkingdata.com/app/h5/v1?appid=${TALKING_DATA_APPID}&vn=${version}&vc=${version}` -export const initBaiduTongJi = () => { - setTimeout(() => { - ipcRenderer.send(BAIDU_TONGJI_INIT) + const head = document.getElementsByTagName('head')[0] + head.appendChild(talkingDataScript) }, 0) } + +ipcRenderer.on(TALKING_DATA_EVENT, (_, data: ITalkingDataOptions) => { + handleTalkingDataEvent(data) +}) diff --git a/src/renderer/utils/common.ts b/src/renderer/utils/common.ts index 19133e6b0..010e74db1 100644 --- a/src/renderer/utils/common.ts +++ b/src/renderer/utils/common.ts @@ -1,9 +1,10 @@ const isDevelopment = process.env.NODE_ENV !== 'production' /* eslint-disable camelcase */ -export const handleBaiduTongJiEvent = (data: IBaiduTongJiOptions) => { - const { category, action, opt_label = '', opt_value = Date.now() } = data - window._hmt.push(['_trackEvent', category, action, opt_label, opt_value]) +export const handleTalkingDataEvent = (data: ITalkingDataOptions) => { + const { EventId, Label = '', MapKv = {} } = data + MapKv.from = window.location.href + window.TDAPP.onEvent(EventId, Label, MapKv) if (isDevelopment) { - console.log('baidu tongji', data) + console.log('talkingData', data) } } diff --git a/src/universal/events/constants.ts b/src/universal/events/constants.ts index a1cba48f4..ead34560d 100644 --- a/src/universal/events/constants.ts +++ b/src/universal/events/constants.ts @@ -1,7 +1,6 @@ export const SHOW_INPUT_BOX = 'SHOW_INPUT_BOX' export const SHOW_INPUT_BOX_RESPONSE = 'SHOW_INPUT_BOX_RESPONSE' export const TOGGLE_SHORTKEY_MODIFIED_MODE = 'TOGGLE_SHORTKEY_MODIFIED_MODE' -export const BAIDU_TONGJI_INIT = 'BAIDU_TONDJI_INIT' -export const BAIDU_TONGJI_INIT_RES = 'BAIDU_TONDJI_INIT_RES' -export const BAIDU_TONGJI_CODE = '19a7ebdbb87f2403773c7ab0cae16d21' -export const BAIDU_TONGJI_EVENT = 'BAIDU_TONGJI_EVENT' +export const TALKING_DATA_APPID = '7E6832BCE3F1438696579E541DFEBFDA' +export const TALKING_DATA_EVENT = 'TALKING_DATA_EVENT' +export const SHOW_PRIVACY_MESSAGE = 'SHOW_PRIVACY_MESSAGE' diff --git a/src/universal/types/shims-tsx.d.ts b/src/universal/types/shims-tsx.d.ts index cf749eb41..fa560a515 100644 --- a/src/universal/types/shims-tsx.d.ts +++ b/src/universal/types/shims-tsx.d.ts @@ -12,6 +12,8 @@ declare global { } interface Window { - _hmt: any[] + TDAPP: { + onEvent: (EventId: string, Label?: string, MapKv?: IStringKeyMap) => void + } } } diff --git a/src/universal/types/types.d.ts b/src/universal/types/types.d.ts index b04bdb2f2..0295b5907 100644 --- a/src/universal/types/types.d.ts +++ b/src/universal/types/types.d.ts @@ -296,13 +296,10 @@ interface IAppNotification { icon?: string } -interface IBaiduTongJiOptions { - category: string - action: string - // eslint-disable-next-line camelcase - opt_label?: string - // eslint-disable-next-line camelcase - opt_value?: number +interface ITalkingDataOptions { + EventId: string + Label?: string + MapKv?: IStringKeyMap } interface IAnalyticsData { @@ -311,3 +308,7 @@ interface IAnalyticsData { count: number duration?: number // 耗时 } + +interface IStringKeyMap { + [propName: string]: any +} diff --git a/vue.config.js b/vue.config.js index efb2583f9..be3d8794d 100644 --- a/vue.config.js +++ b/vue.config.js @@ -13,6 +13,11 @@ const config = { .set('~', resolve('src')) .set('root', resolve('./')) .set('#', resolve('src/universal')) + // define + // config.plugin('define') + // .tap(args => { + // return args + // }) }, pluginOptions: { electronBuilder: {