feat(analytics): Google Analytics・同意モード・一部機能のトラッキング実装 (MisskeyIO#784)
This commit is contained in:
parent
2f4c48bbe6
commit
fcfd004c38
53 changed files with 805 additions and 113 deletions
177
packages/frontend/src/components/MkTrackingConsent.vue
Normal file
177
packages/frontend/src/components/MkTrackingConsent.vue
Normal file
|
@ -0,0 +1,177 @@
|
|||
<template>
|
||||
<div class="_panel _shadow" :class="$style.root">
|
||||
<div :class="$style.main">
|
||||
<div style="display: flex; align-items: center;">
|
||||
<div :class="$style.headerIcon">
|
||||
<i class="ti ti-report-analytics"></i>
|
||||
</div>
|
||||
<div :class="$style.headerTitle"><Mfm :text="i18n.ts.helpUsImproveUserExperience" /></div>
|
||||
</div>
|
||||
<div :class="$style.text">
|
||||
<Mfm
|
||||
:text="i18n.tsx.pleaseConsentToTracking({
|
||||
host: instance.name ?? host,
|
||||
privacyPolicyUrl: instance.privacyPolicyUrl,
|
||||
})"
|
||||
/>
|
||||
</div>
|
||||
<div class="_gaps_s">
|
||||
<div class="_buttons" style="justify-content: right;">
|
||||
<MkButton @click="consentEssential">{{ i18n.ts.consentEssential }}</MkButton>
|
||||
<MkButton primary @click="consentAll">{{ i18n.ts.consentAll }}</MkButton>
|
||||
</div>
|
||||
<MkFolder>
|
||||
<template #icon><i class="ti ti-lock-square"></i></template>
|
||||
<template #label>{{ i18n.ts.gtagConsentCustomize }}</template>
|
||||
<div class="_gaps_s">
|
||||
<MkInfo>{{ i18n.tsx.gtagConsentCustomizeDescription({ host: instance.name ?? host }) }}</MkInfo>
|
||||
<MkSwitch v-model="gtagConsentAnalytics">
|
||||
{{ i18n.ts.gtagConsentAnalytics }}
|
||||
<template #caption>{{ i18n.ts.gtagConsentAnalyticsDescription }}</template>
|
||||
</MkSwitch>
|
||||
<MkSwitch v-model="gtagConsentFunctionality">
|
||||
{{ i18n.ts.gtagConsentFunctionality }}
|
||||
<template #caption>{{ i18n.ts.gtagConsentFunctionalityDescription }}</template>
|
||||
</MkSwitch>
|
||||
<MkSwitch v-model="gtagConsentPersonalization">
|
||||
{{ i18n.ts.gtagConsentPersonalization }}
|
||||
<template #caption>{{ i18n.ts.gtagConsentPersonalizationDescription }}</template>
|
||||
</MkSwitch>
|
||||
<div class="_buttons" style="justify-content: right;">
|
||||
<MkButton @click="consentSelected">{{ i18n.ts.consentSelected }}</MkButton>
|
||||
</div>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import MkFolder from '@/components/MkFolder.vue';
|
||||
import MkSwitch from '@/components/MkSwitch.vue';
|
||||
import MkInfo from '@/components/MkInfo.vue';
|
||||
import { miLocalStorage } from '@/local-storage.js';
|
||||
import { instance } from '@/instance.js';
|
||||
import { host } from '@/config.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { $i } from '@/account.js';
|
||||
import * as os from '@/os.js';
|
||||
import {
|
||||
bootstrap as gtagBootstrap,
|
||||
GtagConsent,
|
||||
GtagConsentParams,
|
||||
set as gtagSet
|
||||
} from 'vue-gtag';
|
||||
|
||||
const emit = defineEmits<(ev: 'closed') => void>();
|
||||
|
||||
const zIndex = os.claimZIndex('middle');
|
||||
|
||||
const gtagConsentAnalytics = ref(false);
|
||||
const gtagConsentFunctionality = ref(false);
|
||||
const gtagConsentPersonalization = ref(false);
|
||||
|
||||
function consentAll() {
|
||||
miLocalStorage.setItem('gaConsent', 'true');
|
||||
const gtagConsent = <GtagConsentParams>{
|
||||
ad_storage: 'granted',
|
||||
ad_user_data: 'granted',
|
||||
ad_personalization: 'granted',
|
||||
analytics_storage: 'granted',
|
||||
functionality_storage: 'granted',
|
||||
personalization_storage: 'granted',
|
||||
security_storage: 'granted',
|
||||
};
|
||||
miLocalStorage.setItemAsJson('gtagConsent', gtagConsent);
|
||||
|
||||
if (typeof window['gtag'] === 'function') (window['gtag'] as GtagConsent)('consent', 'update', gtagConsent);
|
||||
bootstrap();
|
||||
|
||||
emit('closed');
|
||||
}
|
||||
|
||||
function consentEssential() {
|
||||
miLocalStorage.setItem('gaConsent', 'true');
|
||||
const gtagConsent = <GtagConsentParams>{
|
||||
ad_storage: 'denied',
|
||||
ad_user_data: 'denied',
|
||||
ad_personalization: 'denied',
|
||||
analytics_storage: 'denied',
|
||||
functionality_storage: 'denied',
|
||||
personalization_storage: 'denied',
|
||||
security_storage: 'granted',
|
||||
};
|
||||
miLocalStorage.setItemAsJson('gtagConsent', gtagConsent);
|
||||
|
||||
if (typeof window['gtag'] === 'function') (window['gtag'] as GtagConsent)('consent', 'update', gtagConsent);
|
||||
bootstrap();
|
||||
|
||||
emit('closed');
|
||||
}
|
||||
|
||||
function consentSelected() {
|
||||
miLocalStorage.setItem('gaConsent', 'true');
|
||||
const gtagConsent = <GtagConsentParams>{
|
||||
ad_storage: gtagConsentAnalytics.value ? 'granted' : 'denied',
|
||||
ad_user_data: gtagConsentFunctionality.value ? 'granted' : 'denied',
|
||||
ad_personalization: gtagConsentPersonalization.value ? 'granted' : 'denied',
|
||||
analytics_storage: gtagConsentAnalytics.value ? 'granted' : 'denied',
|
||||
functionality_storage: gtagConsentFunctionality.value ? 'granted' : 'denied',
|
||||
personalization_storage: gtagConsentPersonalization.value ? 'granted' : 'denied',
|
||||
security_storage: 'granted',
|
||||
};
|
||||
miLocalStorage.setItemAsJson('gtagConsent', gtagConsent);
|
||||
|
||||
if (typeof window['gtag'] === 'function') (window['gtag'] as GtagConsent)('consent', 'update', gtagConsent);
|
||||
bootstrap();
|
||||
|
||||
emit('closed');
|
||||
}
|
||||
|
||||
function bootstrap() {
|
||||
gtagBootstrap();
|
||||
gtagSet({
|
||||
'client_id': miLocalStorage.getItem('id'),
|
||||
'user_id': $i?.id,
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.root {
|
||||
position: fixed;
|
||||
z-index: v-bind(zIndex);
|
||||
bottom: var(--margin);
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: auto;
|
||||
box-sizing: border-box;
|
||||
width: calc(100% - (var(--margin) * 2));
|
||||
max-width: 500px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.main {
|
||||
padding: 25px 25px 25px 25px;
|
||||
width: inherit;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.headerIcon {
|
||||
margin-right: 8px;
|
||||
font-size: 40px;
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.headerTitle {
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.text {
|
||||
margin: 0.7em 0 1em 0;
|
||||
}
|
||||
</style>
|
|
@ -24,6 +24,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
rel: 'nofollow noopener',
|
||||
target: '_blank',
|
||||
}"
|
||||
@click="onAdClicked"
|
||||
>
|
||||
<img :src="chosen.imageUrl" :class="$style.img">
|
||||
<button class="_button" :class="$style.i" @click.prevent.stop="toggleMenu"><i :class="$style.iIcon" class="ti ti-info-circle"></i></button>
|
||||
|
@ -42,7 +43,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed } from 'vue';
|
||||
/* eslint-disable id-denylist */
|
||||
import { ref, computed, onActivated, onMounted } from 'vue';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { instance } from '@/instance.js';
|
||||
import { url as local, host } from '@/config.js';
|
||||
|
@ -50,6 +52,7 @@ import MkButton from '@/components/MkButton.vue';
|
|||
import { defaultStore } from '@/store.js';
|
||||
import * as os from '@/os.js';
|
||||
import { $i } from '@/account.js';
|
||||
import { usageReport } from '@/scripts/usage-report.js';
|
||||
|
||||
type Ad = (typeof instance)['ads'][number];
|
||||
|
||||
|
@ -123,6 +126,36 @@ function reduceFrequency(): void {
|
|||
chosen.value = choseAd();
|
||||
showMenu.value = false;
|
||||
}
|
||||
|
||||
function onAdClicked(): void {
|
||||
if (chosen.value == null) return;
|
||||
usageReport({
|
||||
t: Math.floor(Date.now() / 1000),
|
||||
e: 'a',
|
||||
i: chosen.value.id,
|
||||
a: 'c',
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (chosen.value == null) return;
|
||||
usageReport({
|
||||
t: Math.floor(Date.now() / 1000),
|
||||
e: 'a',
|
||||
i: chosen.value.id,
|
||||
a: 'v',
|
||||
});
|
||||
});
|
||||
|
||||
onActivated(() => {
|
||||
if (chosen.value == null) return;
|
||||
usageReport({
|
||||
t: Math.floor(Date.now() / 1000),
|
||||
e: 'a',
|
||||
i: chosen.value.id,
|
||||
a: 'v',
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue