diff --git a/CHANGELOG.md b/CHANGELOG.md index 57100aaf3b45..ebfeb8826f82 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 000000000000..5871bf098a3c --- /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 ec0b5360f4a2..7ad6071cebf1 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 ad5e31ad6ff2..9df2f749840d 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 e7ae2ee8e560..1e25c355ca22 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 912c8defbec9..9d5691a427c9 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 { // 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 38ef0d1de837..1cfa9cffa6db 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 { // 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 2e5923819c0d..0d02d46ea49e 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 000000000000..e07a4e9258f2 --- /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) { + return this.analytics?.identify(...args) ?? Promise.resolve(); + } + + public track(...args: Parameters) { + return this.analytics?.track(...args) ?? Promise.resolve(); + } + + public page(...args: Parameters) { + return this.analytics?.page(...args) ?? Promise.resolve(); + } + + public user(...args: Parameters) { + return this.analytics?.user(...args) ?? Promise.resolve(); + } + + public reset(...args: Parameters) { + return this.analytics?.reset(...args) ?? Promise.resolve(); + } + + public ready(...args: Parameters) { + return this.analytics?.ready(...args) ?? function () { void 0; }; + } + + public on(...args: Parameters) { + return this.analytics?.on(...args) ?? function () { void 0; }; + } + + public once(...args: Parameters) { + return this.analytics?.once(...args) ?? function () { void 0; }; + } + + public getState(...args: Parameters) { + 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 1d8e40a12d1d..d09b98efe019 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) { 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 1420b9c26fe2..e725d2a15dc5 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 91f41166e990..a312ecce1236 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 - - +
+ + -
- - - - - - - - Save -
-
+
+ + + + + Save +
+ + + + + +
+ + + + + + + + Save +
+
+
@@ -44,10 +58,13 @@ import MkFolder from '@/components/MkFolder.vue'; const deeplAuthKey = ref(''); const deeplIsPro = ref(false); +const googleAnalyticsMeasurementId = ref(''); + 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 4379b6a31629..3932a8bac8e9 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 c7485c6c3d6e..ae7a8c74406b 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 7524559633d4..55cf69356773 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 @@ -1452,6 +1458,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==} @@ -4374,6 +4404,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==} @@ -4962,6 +4995,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'} @@ -6049,6 +6090,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'} @@ -11080,6 +11124,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': @@ -14700,6 +14780,8 @@ snapshots: '@types/disposable-email-domains@1.0.2': {} + '@types/dlv@1.1.5': {} + '@types/doctrine@0.0.9': {} '@types/eslint@7.29.0': @@ -15432,6 +15514,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: @@ -16660,6 +16755,8 @@ snapshots: disposable-email-domains@1.0.62: {} + dlv@1.1.3: {} + doctrine@2.1.0: dependencies: esutils: 2.0.3