feat(analytics): Google Analytics・同意モード・一部機能のトラッキング実装 (MisskeyIO#784)

This commit is contained in:
あわわわとーにゅ 2024-11-06 01:28:14 +09:00 committed by GitHub
parent 2f4c48bbe6
commit fcfd004c38
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
53 changed files with 805 additions and 113 deletions

View 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>

View file

@ -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>