summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsyuilo <4439005+syuilo@users.noreply.github.com>2025-02-25 20:51:23 +0900
committerGitHub <noreply@github.com>2025-02-25 11:51:23 +0000
commit2b6638e1607e5c44eb6f6dc987e9e893927f1829 (patch)
treef3efba7ca978bd5cf7d32202a014ffb73ac719f6
parentchore(config): migrate renovate config (#15550) (diff)
downloadmisskey-2b6638e1607e5c44eb6f6dc987e9e893927f1829.tar.gz
misskey-2b6638e1607e5c44eb6f6dc987e9e893927f1829.tar.bz2
misskey-2b6638e1607e5c44eb6f6dc987e9e893927f1829.zip
feat: google analytics (#15451)
* wip backend * wip frontend * build misskey-js * implement control panel * fix * introduce analytics wrapper * spdx * Update analytics.ts * Update common.ts * wip * wip * wip * wip * wip * Update CHANGELOG.md --------- Co-authored-by: kakkokari-gtyih <67428053+kakkokari-gtyih@users.noreply.github.com>
-rw-r--r--CHANGELOG.md1
-rw-r--r--packages/backend/migration/1739006797620-GoogleAnalytics.js16
-rw-r--r--packages/backend/src/core/entities/MetaEntityService.ts1
-rw-r--r--packages/backend/src/models/Meta.ts6
-rw-r--r--packages/backend/src/models/json-schema/meta.ts4
-rw-r--r--packages/backend/src/server/api/endpoints/admin/meta.ts5
-rw-r--r--packages/backend/src/server/api/endpoints/admin/update-meta.ts7
-rw-r--r--packages/frontend/package.json2
-rw-r--r--packages/frontend/src/analytics.ts107
-rw-r--r--packages/frontend/src/boot/common.ts16
-rw-r--r--packages/frontend/src/components/MkPageWindow.vue16
-rw-r--r--packages/frontend/src/pages/admin/external-services.vue53
-rw-r--r--packages/frontend/src/router/main.ts9
-rw-r--r--packages/misskey-js/src/autogen/types.ts3
-rw-r--r--pnpm-lock.yaml97
15 files changed, 327 insertions, 16 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d29e3db0d4..2399ce8a52 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,7 @@
### General
- Feat: アクセストークン発行時に通知するように
+- Feat: 実験的なGoogleAnalyticsサポートを追加
- 依存関係の更新
### Client
diff --git a/packages/backend/migration/1739006797620-GoogleAnalytics.js b/packages/backend/migration/1739006797620-GoogleAnalytics.js
new file mode 100644
index 0000000000..5871bf098a
--- /dev/null
+++ b/packages/backend/migration/1739006797620-GoogleAnalytics.js
@@ -0,0 +1,16 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class GoogleAnalytics1739006797620 {
+ name = 'GoogleAnalytics1739006797620'
+
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" ADD "googleAnalyticsMeasurementId" character varying(64)`);
+ }
+
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "googleAnalyticsMeasurementId"`);
+ }
+}
diff --git a/packages/backend/src/core/entities/MetaEntityService.ts b/packages/backend/src/core/entities/MetaEntityService.ts
index ec0b5360f4..7ad6071ceb 100644
--- a/packages/backend/src/core/entities/MetaEntityService.ts
+++ b/packages/backend/src/core/entities/MetaEntityService.ts
@@ -97,6 +97,7 @@ export class MetaEntityService {
enableTurnstile: instance.enableTurnstile,
turnstileSiteKey: instance.turnstileSiteKey,
enableTestcaptcha: instance.enableTestcaptcha,
+ googleAnalyticsMeasurementId: instance.googleAnalyticsMeasurementId,
swPublickey: instance.swPublicKey,
themeColor: instance.themeColor,
mascotImageUrl: instance.mascotImageUrl ?? '/assets/ai.png',
diff --git a/packages/backend/src/models/Meta.ts b/packages/backend/src/models/Meta.ts
index ad5e31ad6f..9df2f74984 100644
--- a/packages/backend/src/models/Meta.ts
+++ b/packages/backend/src/models/Meta.ts
@@ -658,4 +658,10 @@ export class MiMeta {
default: '{}',
})
public federationHosts: string[];
+
+ @Column('varchar', {
+ length: 64,
+ nullable: true,
+ })
+ public googleAnalyticsMeasurementId: string | null;
}
diff --git a/packages/backend/src/models/json-schema/meta.ts b/packages/backend/src/models/json-schema/meta.ts
index e7ae2ee8e5..1e25c355ca 100644
--- a/packages/backend/src/models/json-schema/meta.ts
+++ b/packages/backend/src/models/json-schema/meta.ts
@@ -119,6 +119,10 @@ export const packedMetaLiteSchema = {
type: 'boolean',
optional: false, nullable: false,
},
+ googleAnalyticsMeasurementId: {
+ type: 'string',
+ optional: false, nullable: true,
+ },
swPublickey: {
type: 'string',
optional: false, nullable: true,
diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts
index 912c8defbe..9d5691a427 100644
--- a/packages/backend/src/server/api/endpoints/admin/meta.ts
+++ b/packages/backend/src/server/api/endpoints/admin/meta.ts
@@ -73,6 +73,10 @@ export const meta = {
type: 'boolean',
optional: false, nullable: false,
},
+ googleAnalyticsMeasurementId: {
+ type: 'string',
+ optional: false, nullable: true,
+ },
swPublickey: {
type: 'string',
optional: false, nullable: true,
@@ -572,6 +576,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
enableTurnstile: instance.enableTurnstile,
turnstileSiteKey: instance.turnstileSiteKey,
enableTestcaptcha: instance.enableTestcaptcha,
+ googleAnalyticsMeasurementId: instance.googleAnalyticsMeasurementId,
swPublickey: instance.swPublicKey,
themeColor: instance.themeColor,
mascotImageUrl: instance.mascotImageUrl,
diff --git a/packages/backend/src/server/api/endpoints/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts
index 38ef0d1de8..1cfa9cffa6 100644
--- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts
+++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts
@@ -84,6 +84,7 @@ export const paramDef = {
turnstileSiteKey: { type: 'string', nullable: true },
turnstileSecretKey: { type: 'string', nullable: true },
enableTestcaptcha: { type: 'boolean' },
+ googleAnalyticsMeasurementId: { type: 'string', nullable: true },
sensitiveMediaDetection: { type: 'string', enum: ['none', 'all', 'local', 'remote'] },
sensitiveMediaDetectionSensitivity: { type: 'string', enum: ['medium', 'low', 'high', 'veryLow', 'veryHigh'] },
setSensitiveFlagAutomatically: { type: 'boolean' },
@@ -371,6 +372,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
set.enableTestcaptcha = ps.enableTestcaptcha;
}
+ if (ps.googleAnalyticsMeasurementId !== undefined) {
+ // 空文字列をnullにしたいので??は使わない
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
+ set.googleAnalyticsMeasurementId = ps.googleAnalyticsMeasurementId || null;
+ }
+
if (ps.sensitiveMediaDetection !== undefined) {
set.sensitiveMediaDetection = ps.sensitiveMediaDetection;
}
diff --git a/packages/frontend/package.json b/packages/frontend/package.json
index b2bc47ec83..10fbc58cf0 100644
--- a/packages/frontend/package.json
+++ b/packages/frontend/package.json
@@ -16,6 +16,7 @@
"lint": "pnpm typecheck && pnpm eslint"
},
"dependencies": {
+ "@analytics/google-analytics": "1.1.0",
"@discordapp/twemoji": "15.1.0",
"@github/webauthn-json": "2.1.1",
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
@@ -29,6 +30,7 @@
"@vitejs/plugin-vue": "5.2.1",
"@vue/compiler-sfc": "3.5.13",
"aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.15",
+ "analytics": "0.8.16",
"astring": "1.9.0",
"broadcast-channel": "7.0.0",
"buraha": "0.0.1",
diff --git a/packages/frontend/src/analytics.ts b/packages/frontend/src/analytics.ts
new file mode 100644
index 0000000000..e07a4e9258
--- /dev/null
+++ b/packages/frontend/src/analytics.ts
@@ -0,0 +1,107 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import * as Misskey from 'misskey-js';
+import type { AnalyticsInstance, AnalyticsPlugin } from 'analytics';
+
+/**
+ * analytics moduleを読み込まなくても動作するようにするためのラッパー
+ */
+class AnalyticsProxy implements AnalyticsInstance {
+ private analytics?: AnalyticsInstance;
+
+ constructor(analytics?: AnalyticsInstance) {
+ if (analytics) {
+ this.analytics = analytics;
+ }
+ }
+
+ public setAnalytics(analytics: AnalyticsInstance) {
+ if (this.analytics) {
+ throw new Error('Analytics instance already exists.');
+ }
+ this.analytics = analytics;
+ }
+
+ public identify(...args: Parameters<AnalyticsInstance['identify']>) {
+ return this.analytics?.identify(...args) ?? Promise.resolve();
+ }
+
+ public track(...args: Parameters<AnalyticsInstance['track']>) {
+ return this.analytics?.track(...args) ?? Promise.resolve();
+ }
+
+ public page(...args: Parameters<AnalyticsInstance['page']>) {
+ return this.analytics?.page(...args) ?? Promise.resolve();
+ }
+
+ public user(...args: Parameters<AnalyticsInstance['user']>) {
+ return this.analytics?.user(...args) ?? Promise.resolve();
+ }
+
+ public reset(...args: Parameters<AnalyticsInstance['reset']>) {
+ return this.analytics?.reset(...args) ?? Promise.resolve();
+ }
+
+ public ready(...args: Parameters<AnalyticsInstance['ready']>) {
+ return this.analytics?.ready(...args) ?? function () { void 0; };
+ }
+
+ public on(...args: Parameters<AnalyticsInstance['on']>) {
+ return this.analytics?.on(...args) ?? function () { void 0; };
+ }
+
+ public once(...args: Parameters<AnalyticsInstance['once']>) {
+ return this.analytics?.once(...args) ?? function () { void 0; };
+ }
+
+ public getState(...args: Parameters<AnalyticsInstance['getState']>) {
+ return this.analytics?.getState(...args) ?? Promise.resolve();
+ }
+
+ public get storage() {
+ return this.analytics?.storage ?? {
+ getItem: () => null,
+ setItem: () => void 0,
+ removeItem: () => void 0,
+ };
+ }
+
+ public get plugins() {
+ return this.analytics?.plugins ?? {
+ enable: (p, c) => Promise.resolve(c ? c() : void 0),
+ disable: (p, c) => Promise.resolve(c ? c() : void 0),
+ };
+ }
+}
+
+export const analytics = new AnalyticsProxy();
+
+export async function initAnalytics(instance: Misskey.entities.MetaDetailed) {
+ // アナリティクスプロバイダに関する設定がひとつもない場合は、アナリティクスモジュールを読み込まない
+ if (!instance.googleAnalyticsMeasurementId) {
+ return;
+ }
+
+ const { default: Analytics } = await import('analytics');
+ const plugins: AnalyticsPlugin[] = [];
+
+ // Google Analytics
+ if (instance.googleAnalyticsMeasurementId) {
+ const { default: googleAnalytics } = await import('@analytics/google-analytics');
+
+ plugins.push(googleAnalytics({
+ measurementIds: [instance.googleAnalyticsMeasurementId],
+ debug: _DEV_,
+ }));
+ }
+
+ analytics.setAnalytics(Analytics({
+ app: 'misskey',
+ version: _VERSION_,
+ debug: _DEV_,
+ plugins,
+ }));
+}
diff --git a/packages/frontend/src/boot/common.ts b/packages/frontend/src/boot/common.ts
index 1d8e40a12d..d09b98efe0 100644
--- a/packages/frontend/src/boot/common.ts
+++ b/packages/frontend/src/boot/common.ts
@@ -4,9 +4,9 @@
*/
import { computed, watch, version as vueVersion } from 'vue';
-import type { App } from 'vue';
import { compareVersions } from 'compare-versions';
import { version, lang, updateLocale, locale } from '@@/js/config.js';
+import type { App } from 'vue';
import widgets from '@/widgets/index.js';
import directives from '@/directives/index.js';
import components from '@/components/index.js';
@@ -21,6 +21,7 @@ import { reloadChannel } from '@/scripts/unison-reload.js';
import { getUrlWithoutLoginId } from '@/scripts/login-id.js';
import { getAccountFromId } from '@/scripts/get-account-from-id.js';
import { deckStore } from '@/ui/deck/deck-store.js';
+import { analytics, initAnalytics } from '@/analytics.js';
import { miLocalStorage } from '@/local-storage.js';
import { fetchCustomEmojis } from '@/custom-emojis.js';
import { setupRouter } from '@/router/main.js';
@@ -241,6 +242,19 @@ export async function common(createVue: () => App<Element>) {
await fetchCustomEmojis();
} catch (err) { /* empty */ }
+ // analytics
+ fetchInstanceMetaPromise.then(async () => {
+ await initAnalytics(instance);
+
+ if ($i) {
+ analytics.identify($i.id);
+ }
+
+ analytics.page({
+ path: window.location.pathname,
+ });
+ });
+
const app = createVue();
setupRouter(app, createMainRouter);
diff --git a/packages/frontend/src/components/MkPageWindow.vue b/packages/frontend/src/components/MkPageWindow.vue
index 1420b9c26f..e725d2a15d 100644
--- a/packages/frontend/src/components/MkPageWindow.vue
+++ b/packages/frontend/src/components/MkPageWindow.vue
@@ -32,6 +32,7 @@ SPDX-License-Identifier: AGPL-3.0-only
import { computed, onMounted, onUnmounted, provide, ref, shallowRef } from 'vue';
import { url } from '@@/js/config.js';
import { getScrollContainer } from '@@/js/scroll.js';
+import type { PageMetadata } from '@/scripts/page-metadata.js';
import RouterView from '@/components/global/RouterView.vue';
import MkWindow from '@/components/MkWindow.vue';
import { popout as _popout } from '@/scripts/popout.js';
@@ -39,11 +40,11 @@ import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
import { useScrollPositionManager } from '@/nirax.js';
import { i18n } from '@/i18n.js';
import { provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
-import type { PageMetadata } from '@/scripts/page-metadata.js';
import { openingWindowsCount } from '@/os.js';
import { claimAchievement } from '@/scripts/achievements.js';
import { useRouterFactory } from '@/router/supplier.js';
import { mainRouter } from '@/router/main.js';
+import { analytics } from '@/analytics.js';
const props = defineProps<{
initialPath: string;
@@ -99,6 +100,14 @@ windowRouter.addListener('replace', ctx => {
history.value.push({ path: ctx.path, key: ctx.key });
});
+windowRouter.addListener('change', ctx => {
+ console.log('windowRouter: change', ctx.path);
+ analytics.page({
+ path: ctx.path,
+ title: ctx.path,
+ });
+});
+
windowRouter.init();
provide('router', windowRouter);
@@ -160,6 +169,11 @@ function popout() {
useScrollPositionManager(() => getScrollContainer(contents.value), windowRouter);
onMounted(() => {
+ analytics.page({
+ path: props.initialPath,
+ title: props.initialPath,
+ });
+
openingWindowsCount.value++;
if (openingWindowsCount.value >= 3) {
claimAchievement('open3windows');
diff --git a/packages/frontend/src/pages/admin/external-services.vue b/packages/frontend/src/pages/admin/external-services.vue
index 91f41166e9..a312ecce12 100644
--- a/packages/frontend/src/pages/admin/external-services.vue
+++ b/packages/frontend/src/pages/admin/external-services.vue
@@ -8,20 +8,34 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #header><XHeader :actions="headerActions" :tabs="headerTabs"/></template>
<MkSpacer :contentMax="700" :marginMin="16" :marginMax="32">
<FormSuspense :p="init">
- <MkFolder>
- <template #label>DeepL Translation</template>
+ <div class="_gaps_m">
+ <MkFolder>
+ <template #label>Google Analytics<span class="_beta">{{ i18n.ts.beta }}</span></template>
- <div class="_gaps_m">
- <MkInput v-model="deeplAuthKey">
- <template #prefix><i class="ti ti-key"></i></template>
- <template #label>DeepL Auth Key</template>
- </MkInput>
- <MkSwitch v-model="deeplIsPro">
- <template #label>Pro account</template>
- </MkSwitch>
- <MkButton primary @click="save_deepl">Save</MkButton>
- </div>
- </MkFolder>
+ <div class="_gaps_m">
+ <MkInput v-model="googleAnalyticsMeasurementId">
+ <template #prefix><i class="ti ti-key"></i></template>
+ <template #label>Measurement ID</template>
+ </MkInput>
+ <MkButton primary @click="save_googleAnalytics">Save</MkButton>
+ </div>
+ </MkFolder>
+
+ <MkFolder>
+ <template #label>DeepL Translation</template>
+
+ <div class="_gaps_m">
+ <MkInput v-model="deeplAuthKey">
+ <template #prefix><i class="ti ti-key"></i></template>
+ <template #label>DeepL Auth Key</template>
+ </MkInput>
+ <MkSwitch v-model="deeplIsPro">
+ <template #label>Pro account</template>
+ </MkSwitch>
+ <MkButton primary @click="save_deepl">Save</MkButton>
+ </div>
+ </MkFolder>
+ </div>
</FormSuspense>
</MkSpacer>
</MkStickyContainer>
@@ -44,10 +58,13 @@ import MkFolder from '@/components/MkFolder.vue';
const deeplAuthKey = ref<string>('');
const deeplIsPro = ref<boolean>(false);
+const googleAnalyticsMeasurementId = ref<string>('');
+
async function init() {
const meta = await misskeyApi('admin/meta');
- deeplAuthKey.value = meta.deeplAuthKey;
+ deeplAuthKey.value = meta.deeplAuthKey ?? '';
deeplIsPro.value = meta.deeplIsPro;
+ googleAnalyticsMeasurementId.value = meta.googleAnalyticsMeasurementId ?? '';
}
function save_deepl() {
@@ -59,6 +76,14 @@ function save_deepl() {
});
}
+function save_googleAnalytics() {
+ os.apiWithDialog('admin/update-meta', {
+ googleAnalyticsMeasurementId: googleAnalyticsMeasurementId.value,
+ }).then(() => {
+ fetchInstance(true);
+ });
+}
+
const headerActions = computed(() => []);
const headerTabs = computed(() => []);
diff --git a/packages/frontend/src/router/main.ts b/packages/frontend/src/router/main.ts
index 4379b6a316..3932a8bac8 100644
--- a/packages/frontend/src/router/main.ts
+++ b/packages/frontend/src/router/main.ts
@@ -7,6 +7,7 @@ import { EventEmitter } from 'eventemitter3';
import type { IRouter, Resolved, RouteDef, RouterEvent, RouterFlag } from '@/nirax.js';
import type { App, ShallowRef } from 'vue';
+import { analytics } from '@/analytics.js';
/**
* {@link Router}による画面遷移を可能とするために{@link mainRouter}をセットアップする。
@@ -29,6 +30,14 @@ export function setupRouter(app: App, routerFactory: ((path: string) => IRouter)
window.history.replaceState({ key: ctx.key }, '', ctx.path);
});
+ mainRouter.addListener('change', ctx => {
+ console.log('mainRouter: change', ctx.path);
+ analytics.page({
+ path: ctx.path,
+ title: ctx.path,
+ });
+ });
+
mainRouter.init();
setMainRouter(mainRouter);
diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts
index c7485c6c3d..ae7a8c7440 100644
--- a/packages/misskey-js/src/autogen/types.ts
+++ b/packages/misskey-js/src/autogen/types.ts
@@ -5042,6 +5042,7 @@ export type components = {
enableTurnstile: boolean;
turnstileSiteKey: string | null;
enableTestcaptcha: boolean;
+ googleAnalyticsMeasurementId: string | null;
swPublickey: string | null;
/** @default /assets/ai.png */
mascotImageUrl: string;
@@ -8251,6 +8252,7 @@ export type operations = {
enableTurnstile: boolean;
turnstileSiteKey: string | null;
enableTestcaptcha: boolean;
+ googleAnalyticsMeasurementId: string | null;
swPublickey: string | null;
/** @default /assets/ai.png */
mascotImageUrl: string | null;
@@ -10617,6 +10619,7 @@ export type operations = {
turnstileSiteKey?: string | null;
turnstileSecretKey?: string | null;
enableTestcaptcha?: boolean;
+ googleAnalyticsMeasurementId?: string | null;
/** @enum {string} */
sensitiveMediaDetection?: 'none' | 'all' | 'local' | 'remote';
/** @enum {string} */
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index dd5391a218..45f28ac7bb 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -685,6 +685,9 @@ importers:
packages/frontend:
dependencies:
+ '@analytics/google-analytics':
+ specifier: 1.1.0
+ version: 1.1.0
'@discordapp/twemoji':
specifier: 15.1.0
version: 15.1.0
@@ -724,6 +727,9 @@ importers:
aiscript-vscode:
specifier: github:aiscript-dev/aiscript-vscode#v0.1.15
version: https://codeload.github.com/aiscript-dev/aiscript-vscode/tar.gz/c3cde89e79a41d93540cf8a48cd619c3f2dcb1b7
+ analytics:
+ specifier: 0.8.16
+ version: 0.8.16(@types/dlv@1.1.5)
astring:
specifier: 1.9.0
version: 1.9.0
@@ -1448,6 +1454,30 @@ packages:
resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==}
engines: {node: '>=6.0.0'}
+ '@analytics/cookie-utils@0.2.12':
+ resolution: {integrity: sha512-2h/yuIu3kmu+ZJlKmlT6GoRvUEY2k1BbQBezEv5kGhnn9KpmzPz715Y3GmM2i+m7Y0QmBdVUoA260dQZkofs2A==}
+
+ '@analytics/core@0.12.17':
+ resolution: {integrity: sha512-GMxRm5Dp3Wam/w5NNvqNKMO6zWecozbVv21Kn4WhftCx6OjJI7zMlVtiLpjGjxa0RRZfVG80YhupF0Qh9XL2gw==}
+
+ '@analytics/global-storage-utils@0.1.7':
+ resolution: {integrity: sha512-V+spzGLZYm4biZT4uefaylm80SrLXf8WOTv9hCgA46cLcyxx3LD4GCpssp1lj+RcWLl/uXJQBRO4Mnn/o1x6Gw==}
+
+ '@analytics/google-analytics@1.1.0':
+ resolution: {integrity: sha512-i8uGyELMtwEUAf3GNWNLNBzhRvReDn1RUxvMdMhjUA7+GNGxPOM4kkzFfv3giQXKNxTEjfsh75kqNcscbJsuaA==}
+
+ '@analytics/localstorage-utils@0.1.10':
+ resolution: {integrity: sha512-uJS+Jp1yLG5VFCgA5T82ZODYBS0xuDQx0NtAZrgbqt9j51BX3TcgmOez5LVkrUNu/lpbxjCLq35I4TKj78VmOQ==}
+
+ '@analytics/session-storage-utils@0.0.7':
+ resolution: {integrity: sha512-PSv40UxG96HVcjY15e3zOqU2n8IqXnH8XvTkg1X43uXNTKVSebiI2kUjA3Q7ESFbw5DPwcLbJhV7GforpuBLDw==}
+
+ '@analytics/storage-utils@0.4.2':
+ resolution: {integrity: sha512-AXObwyVQw9h2uJh1t2hUgabtVxzYpW+7uKVbdHQK80vr3Td5rrmCxrCxarh7HUuAgSDZ0bZWqmYxVgmwKceaLg==}
+
+ '@analytics/type-utils@0.6.2':
+ resolution: {integrity: sha512-TD+xbmsBLyYy/IxFimW/YL/9L2IEnM7/EoV9Aeh56U64Ify8o27HJcKjo38XY9Tcn0uOq1AX3thkKgvtWvwFQg==}
+
'@apidevtools/swagger-methods@3.0.2':
resolution: {integrity: sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==}
@@ -4226,6 +4256,9 @@ packages:
'@types/disposable-email-domains@1.0.2':
resolution: {integrity: sha512-SDKwyYTjk3y5aZBxxc38yRecpJPjsqn57STz1bNxYYlv4k11bBe7QB8w4llXDTmQXKT1mFvgGmJv+8Zdu3YmJw==}
+ '@types/dlv@1.1.5':
+ resolution: {integrity: sha512-JHOWNfiWepAhfwlSw17kiWrWrk6od2dEQgHltJw9AS0JPFoLZJBge5+Dnil2NfdjAvJ/+vGSX60/BRW20PpUXw==}
+
'@types/doctrine@0.0.9':
resolution: {integrity: sha512-eOIHzCUSH7SMfonMG1LsC2f8vxBFtho6NGBznK41R84YzPuvSBzrhEps33IsQiOW9+VL6NQ9DbjQJznk/S4uRA==}
@@ -4864,6 +4897,14 @@ packages:
alien-signals@1.0.3:
resolution: {integrity: sha512-zQOh3wAYK5ujENxvBBR3CFGF/b6afaSzZ/c9yNhJ1ENrGHETvpUuKQsa93Qrclp0+PzTF93MaZ7scVp1uUozhA==}
+ analytics-utils@1.0.14:
+ resolution: {integrity: sha512-9v0kPd8v0GuBvfQcg5BO48AElaEAr9IXMAfJWXYMAhrD3QprgozEIUgMp/de0vS136PUOBB+10XQH9eBgBmfMw==}
+ peerDependencies:
+ '@types/dlv': ^1.0.0
+
+ analytics@0.8.16:
+ resolution: {integrity: sha512-LEFQ47G9V1zVp9WIh2xhnbmSFEJq+WEzSv6voJ5uba88lefiIIYeG2nq87gFu83ocz1qtb9u7XgeaKKVBbbgWA==}
+
ansi-colors@4.1.3:
resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==}
engines: {node: '>=6'}
@@ -5947,6 +5988,9 @@ packages:
disposable-email-domains@1.0.62:
resolution: {integrity: sha512-LBQvhRw7mznQTPoyZbsmYeNOZt1pN5aCsx4BAU/3siVFuiM9f2oyKzUaB8v1jbxFjE3aYqYiMo63kAL4pHgfWQ==}
+ dlv@1.1.3:
+ resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==}
+
doctrine@2.1.0:
resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==}
engines: {node: '>=0.10.0'}
@@ -10953,6 +10997,42 @@ snapshots:
'@jridgewell/gen-mapping': 0.3.5
'@jridgewell/trace-mapping': 0.3.25
+ '@analytics/cookie-utils@0.2.12':
+ dependencies:
+ '@analytics/global-storage-utils': 0.1.7
+
+ '@analytics/core@0.12.17(@types/dlv@1.1.5)':
+ dependencies:
+ '@analytics/global-storage-utils': 0.1.7
+ '@analytics/type-utils': 0.6.2
+ analytics-utils: 1.0.14(@types/dlv@1.1.5)
+ transitivePeerDependencies:
+ - '@types/dlv'
+
+ '@analytics/global-storage-utils@0.1.7':
+ dependencies:
+ '@analytics/type-utils': 0.6.2
+
+ '@analytics/google-analytics@1.1.0': {}
+
+ '@analytics/localstorage-utils@0.1.10':
+ dependencies:
+ '@analytics/global-storage-utils': 0.1.7
+
+ '@analytics/session-storage-utils@0.0.7':
+ dependencies:
+ '@analytics/global-storage-utils': 0.1.7
+
+ '@analytics/storage-utils@0.4.2':
+ dependencies:
+ '@analytics/cookie-utils': 0.2.12
+ '@analytics/global-storage-utils': 0.1.7
+ '@analytics/localstorage-utils': 0.1.10
+ '@analytics/session-storage-utils': 0.0.7
+ '@analytics/type-utils': 0.6.2
+
+ '@analytics/type-utils@0.6.2': {}
+
'@apidevtools/swagger-methods@3.0.2': {}
'@asamuzakjp/css-color@2.8.3':
@@ -14499,6 +14579,8 @@ snapshots:
'@types/disposable-email-domains@1.0.2': {}
+ '@types/dlv@1.1.5': {}
+
'@types/doctrine@0.0.9': {}
'@types/eslint@7.29.0':
@@ -15312,6 +15394,19 @@ snapshots:
alien-signals@1.0.3: {}
+ analytics-utils@1.0.14(@types/dlv@1.1.5):
+ dependencies:
+ '@analytics/type-utils': 0.6.2
+ '@types/dlv': 1.1.5
+ dlv: 1.1.3
+
+ analytics@0.8.16(@types/dlv@1.1.5):
+ dependencies:
+ '@analytics/core': 0.12.17(@types/dlv@1.1.5)
+ '@analytics/storage-utils': 0.4.2
+ transitivePeerDependencies:
+ - '@types/dlv'
+
ansi-colors@4.1.3: {}
ansi-escapes@4.3.2:
@@ -16547,6 +16642,8 @@ snapshots:
disposable-email-domains@1.0.62: {}
+ dlv@1.1.3: {}
+
doctrine@2.1.0:
dependencies:
esutils: 2.0.3