build: switch from yarn to npm to manage js dependencies and move js contents to root
yarn v1 is being deprecated and starts to have some issues Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
175
src/services/AnonymousParticipationStorage.ts
Normal file
175
src/services/AnonymousParticipationStorage.ts
Normal file
@@ -0,0 +1,175 @@
|
||||
import { IEvent } from "@/types/event.model";
|
||||
|
||||
const ANONYMOUS_PARTICIPATIONS_LOCALSTORAGE_KEY = "ANONYMOUS_PARTICIPATIONS";
|
||||
|
||||
interface IAnonymousParticipation {
|
||||
token: string;
|
||||
expiration: Date;
|
||||
confirmed: boolean;
|
||||
}
|
||||
|
||||
class AnonymousParticipationNotFoundError extends Error {
|
||||
constructor(message?: string) {
|
||||
super(message);
|
||||
Object.setPrototypeOf(this, new.target.prototype);
|
||||
this.name = AnonymousParticipationNotFoundError.name;
|
||||
}
|
||||
}
|
||||
|
||||
function jsonToMap(jsonStr: string): Map<string, IAnonymousParticipation> {
|
||||
return new Map(JSON.parse(jsonStr));
|
||||
}
|
||||
|
||||
function mapToJson(map: Map<any, any>): string {
|
||||
return JSON.stringify([...map]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch existing anonymous participations saved inside this browser
|
||||
*/
|
||||
function getLocalAnonymousParticipations(): Map<
|
||||
string,
|
||||
IAnonymousParticipation
|
||||
> {
|
||||
return jsonToMap(
|
||||
localStorage.getItem(ANONYMOUS_PARTICIPATIONS_LOCALSTORAGE_KEY) ||
|
||||
mapToJson(new Map())
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Purge participations which expiration has been reached
|
||||
* @param participations Map
|
||||
*/
|
||||
function purgeOldParticipations(
|
||||
participations: Map<string, IAnonymousParticipation>
|
||||
): Map<string, IAnonymousParticipation> {
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const [hashedUUID, { expiration }] of participations) {
|
||||
if (expiration < new Date()) {
|
||||
participations.delete(hashedUUID);
|
||||
}
|
||||
}
|
||||
return participations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a participation in the list of anonymous participations
|
||||
* @param hashedUUID
|
||||
* @param participation
|
||||
*/
|
||||
function insertLocalAnonymousParticipation(
|
||||
hashedUUID: string,
|
||||
participation: IAnonymousParticipation
|
||||
) {
|
||||
const participations = purgeOldParticipations(
|
||||
getLocalAnonymousParticipations()
|
||||
);
|
||||
participations.set(hashedUUID, participation);
|
||||
localStorage.setItem(
|
||||
ANONYMOUS_PARTICIPATIONS_LOCALSTORAGE_KEY,
|
||||
mapToJson(participations)
|
||||
);
|
||||
}
|
||||
|
||||
function buildExpiration(event: IEvent): Date {
|
||||
const expiration = new Date(event.endsOn ?? event.beginsOn);
|
||||
expiration.setMonth(expiration.getMonth() + 1);
|
||||
return expiration;
|
||||
}
|
||||
|
||||
async function digestMessage(message: string): Promise<string> {
|
||||
const encoder = new TextEncoder();
|
||||
const data = encoder.encode(message);
|
||||
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
|
||||
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
||||
return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
|
||||
}
|
||||
|
||||
async function addLocalUnconfirmedAnonymousParticipation(
|
||||
event: IEvent,
|
||||
cancellationToken: string
|
||||
): Promise<void> {
|
||||
/**
|
||||
* We hash the event UUID so that we can't know which events
|
||||
* an anonymous user goes by looking up it's localstorage
|
||||
*/
|
||||
const hashedUUID = await digestMessage(event.uuid);
|
||||
|
||||
/**
|
||||
* We round expiration to first day of next 3 months so that
|
||||
* it's difficult to find event from date
|
||||
*/
|
||||
const expiration = buildExpiration(event);
|
||||
insertLocalAnonymousParticipation(hashedUUID, {
|
||||
token: cancellationToken,
|
||||
expiration,
|
||||
confirmed: false,
|
||||
});
|
||||
}
|
||||
|
||||
async function confirmLocalAnonymousParticipation(uuid: string): Promise<void> {
|
||||
const participations = purgeOldParticipations(
|
||||
getLocalAnonymousParticipations()
|
||||
);
|
||||
const hashedUUID = await digestMessage(uuid);
|
||||
const participation = participations.get(hashedUUID);
|
||||
if (participation) {
|
||||
participation.confirmed = true;
|
||||
participations.set(hashedUUID, participation);
|
||||
localStorage.setItem(
|
||||
ANONYMOUS_PARTICIPATIONS_LOCALSTORAGE_KEY,
|
||||
mapToJson(participations)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function getParticipation(
|
||||
eventUUID: string
|
||||
): Promise<IAnonymousParticipation> {
|
||||
const hashedUUID = await digestMessage(eventUUID);
|
||||
const participation = purgeOldParticipations(
|
||||
getLocalAnonymousParticipations()
|
||||
).get(hashedUUID);
|
||||
if (participation) {
|
||||
return participation;
|
||||
}
|
||||
throw new AnonymousParticipationNotFoundError("Participation not found");
|
||||
}
|
||||
|
||||
async function isParticipatingInThisEvent(eventUUID: string): Promise<boolean> {
|
||||
const participation = await getParticipation(eventUUID);
|
||||
return participation !== undefined && participation.confirmed;
|
||||
}
|
||||
|
||||
async function getLeaveTokenForParticipation(
|
||||
eventUUID: string
|
||||
): Promise<string> {
|
||||
return (await getParticipation(eventUUID)).token;
|
||||
}
|
||||
|
||||
async function removeAnonymousParticipation(eventUUID: string): Promise<void> {
|
||||
const hashedUUID = await digestMessage(eventUUID);
|
||||
const participations = purgeOldParticipations(
|
||||
getLocalAnonymousParticipations()
|
||||
);
|
||||
participations.delete(hashedUUID);
|
||||
localStorage.setItem(
|
||||
ANONYMOUS_PARTICIPATIONS_LOCALSTORAGE_KEY,
|
||||
mapToJson(participations)
|
||||
);
|
||||
}
|
||||
|
||||
function removeAllAnonymousParticipations(): void {
|
||||
localStorage.removeItem(ANONYMOUS_PARTICIPATIONS_LOCALSTORAGE_KEY);
|
||||
}
|
||||
|
||||
export {
|
||||
addLocalUnconfirmedAnonymousParticipation,
|
||||
confirmLocalAnonymousParticipation,
|
||||
getLeaveTokenForParticipation,
|
||||
isParticipatingInThisEvent,
|
||||
removeAnonymousParticipation,
|
||||
removeAllAnonymousParticipations,
|
||||
AnonymousParticipationNotFoundError,
|
||||
};
|
||||
294
src/services/EventMetadata.ts
Normal file
294
src/services/EventMetadata.ts
Normal file
@@ -0,0 +1,294 @@
|
||||
import {
|
||||
EventMetadataType,
|
||||
EventMetadataKeyType,
|
||||
EventMetadataCategories,
|
||||
} from "@/types/enums";
|
||||
import { IEventMetadataDescription } from "@/types/event-metadata";
|
||||
import { i18n } from "@/utils/i18n";
|
||||
|
||||
const t = i18n.global.t;
|
||||
|
||||
export const eventMetaDataList: IEventMetadataDescription[] = [
|
||||
{
|
||||
icon: "wheelchair-accessibility",
|
||||
key: "mz:accessibility:wheelchairAccessible",
|
||||
label: t("Wheelchair accessibility") as string,
|
||||
description: t(
|
||||
"Whether the event is accessible with a wheelchair"
|
||||
) as string,
|
||||
value: "no",
|
||||
type: EventMetadataType.STRING,
|
||||
keyType: EventMetadataKeyType.CHOICE,
|
||||
choices: {
|
||||
no: t("Not accessible with a wheelchair") as string,
|
||||
partially: t("Partially accessible with a wheelchair") as string,
|
||||
fully: t("Fully accessible with a wheelchair") as string,
|
||||
},
|
||||
category: EventMetadataCategories.ACCESSIBILITY,
|
||||
},
|
||||
{
|
||||
icon: "subtitles",
|
||||
key: "mz:accessibility:live:subtitle",
|
||||
label: t("Subtitles") as string,
|
||||
description: t("Whether the event live video is subtitled") as string,
|
||||
value: "false",
|
||||
type: EventMetadataType.BOOLEAN,
|
||||
keyType: EventMetadataKeyType.PLAIN,
|
||||
choices: {
|
||||
true: t("The event live video contains subtitles") as string,
|
||||
false: t("The event live video does not contain subtitles") as string,
|
||||
},
|
||||
category: EventMetadataCategories.ACCESSIBILITY,
|
||||
},
|
||||
{
|
||||
icon: "mz:icon:sign_language",
|
||||
key: "mz:accessibility:live:sign_language",
|
||||
label: t("Sign Language") as string,
|
||||
description: t(
|
||||
"Whether the event is interpreted in sign language"
|
||||
) as string,
|
||||
value: "false",
|
||||
type: EventMetadataType.BOOLEAN,
|
||||
keyType: EventMetadataKeyType.PLAIN,
|
||||
choices: {
|
||||
true: t("The event has a sign language interpreter") as string,
|
||||
false: t("The event hasn't got a sign language interpreter") as string,
|
||||
},
|
||||
category: EventMetadataCategories.ACCESSIBILITY,
|
||||
},
|
||||
{
|
||||
icon: "smoking-off",
|
||||
key: "mz:accessibility:smokeFree",
|
||||
label: t("Smoke free") as string,
|
||||
description: t("Whether smoking is prohibited during the event") as string,
|
||||
value: "false",
|
||||
type: EventMetadataType.BOOLEAN,
|
||||
keyType: EventMetadataKeyType.PLAIN,
|
||||
choices: {
|
||||
true: t("Smoke free") as string,
|
||||
false: t("Smoking allowed") as string,
|
||||
},
|
||||
category: EventMetadataCategories.ACCESSIBILITY,
|
||||
},
|
||||
{
|
||||
icon: "youtube",
|
||||
key: "mz:replay:youtube:url",
|
||||
label: t("YouTube replay") as string,
|
||||
description: t(
|
||||
"The URL where the event live can be watched again after it has ended"
|
||||
) as string,
|
||||
value: "",
|
||||
type: EventMetadataType.STRING,
|
||||
keyType: EventMetadataKeyType.URL,
|
||||
pattern:
|
||||
/http(?:s?):\/\/(?:www\.)?youtu(?:be\.com\/watch\?v=|\.be\/)([\w\-_]*)(&(amp;)?[\w?=]*)?/,
|
||||
category: EventMetadataCategories.REPLAY,
|
||||
},
|
||||
// {
|
||||
// icon: "twitch",
|
||||
// key: "mz:replay:twitch:url",
|
||||
// label: t("Twitch replay") as string,
|
||||
// description: t(
|
||||
// "The URL where the event live can be watched again after it has ended"
|
||||
// ) as string,
|
||||
// value: "",
|
||||
// type: EventMetadataType.STRING,
|
||||
// },
|
||||
{
|
||||
icon: "mz:icon:peertube",
|
||||
key: "mz:replay:peertube:url",
|
||||
label: t("PeerTube replay") as string,
|
||||
description: t(
|
||||
"The URL where the event live can be watched again after it has ended"
|
||||
) as string,
|
||||
value: "",
|
||||
type: EventMetadataType.STRING,
|
||||
keyType: EventMetadataKeyType.URL,
|
||||
pattern: /^https?:\/\/([^/]+)\/(?:videos\/(?:watch|embed)|w)\/([^/]+)$/,
|
||||
category: EventMetadataCategories.REPLAY,
|
||||
},
|
||||
{
|
||||
icon: "mz:icon:peertube",
|
||||
key: "mz:live:peertube:url",
|
||||
label: t("PeerTube live") as string,
|
||||
description: t("The URL where the event can be watched live") as string,
|
||||
value: "",
|
||||
type: EventMetadataType.STRING,
|
||||
keyType: EventMetadataKeyType.URL,
|
||||
pattern: /^https?:\/\/([^/]+)\/(?:videos\/(?:watch|embed)|w)\/([^/]+)$/,
|
||||
category: EventMetadataCategories.LIVE,
|
||||
},
|
||||
{
|
||||
icon: "twitch",
|
||||
key: "mz:live:twitch:url",
|
||||
label: t("Twitch live") as string,
|
||||
description: t("The URL where the event can be watched live") as string,
|
||||
value: "",
|
||||
type: EventMetadataType.STRING,
|
||||
keyType: EventMetadataKeyType.URL,
|
||||
placeholder: "https://www.twitch.tv/",
|
||||
pattern: /^(?:https?:\/\/)?(?:www\.|go\.)?twitch\.tv\/([a-z0-9_]+)($|\?)/,
|
||||
category: EventMetadataCategories.LIVE,
|
||||
},
|
||||
{
|
||||
icon: "youtube",
|
||||
key: "mz:live:youtube:url",
|
||||
label: t("YouTube live") as string,
|
||||
description: t("The URL where the event can be watched live") as string,
|
||||
value: "",
|
||||
type: EventMetadataType.STRING,
|
||||
keyType: EventMetadataKeyType.URL,
|
||||
pattern:
|
||||
/http(?:s?):\/\/(?:www\.)?youtu(?:be\.com\/watch\?v=|\.be\/)([\w\-_]*)(&(amp;)?[\w?=]*)?/,
|
||||
category: EventMetadataCategories.LIVE,
|
||||
},
|
||||
{
|
||||
icon: "mz:icon:owncast",
|
||||
key: "mz:live:owncast:url",
|
||||
label: t("Owncast") as string,
|
||||
description: t("The URL where the event can be watched live") as string,
|
||||
value: "",
|
||||
type: EventMetadataType.STRING,
|
||||
keyType: EventMetadataKeyType.URL,
|
||||
pattern: /^https?:\/\/(([^/.]+)\.)+([a-z]+)\/?/,
|
||||
category: EventMetadataCategories.LIVE,
|
||||
},
|
||||
{
|
||||
icon: "calendar-check",
|
||||
key: "mz:poll:framadate:url",
|
||||
label: t("Framadate poll") as string,
|
||||
description: t(
|
||||
"The URL of a poll where the choice for the event date is happening"
|
||||
) as string,
|
||||
value: "",
|
||||
placeholder: "https://framadate.org/",
|
||||
type: EventMetadataType.STRING,
|
||||
keyType: EventMetadataKeyType.URL,
|
||||
category: EventMetadataCategories.TOOLS,
|
||||
},
|
||||
{
|
||||
icon: "file-document-edit",
|
||||
key: "mz:notes:etherpad:url",
|
||||
label: t("Etherpad notes") as string,
|
||||
description: t(
|
||||
"The URL of a pad where notes are being taken collaboratively"
|
||||
) as string,
|
||||
value: "",
|
||||
placeholder: t(
|
||||
"https://mensuel.framapad.org/p/some-secret-token"
|
||||
) as string,
|
||||
type: EventMetadataType.STRING,
|
||||
keyType: EventMetadataKeyType.URL,
|
||||
category: EventMetadataCategories.TOOLS,
|
||||
},
|
||||
{
|
||||
icon: "twitter",
|
||||
key: "mz:social:twitter:account",
|
||||
label: t("Twitter account") as string,
|
||||
description: t(
|
||||
"A twitter account handle to follow for event updates"
|
||||
) as string,
|
||||
value: "",
|
||||
placeholder: "@JoinMobilizon",
|
||||
type: EventMetadataType.STRING,
|
||||
keyType: EventMetadataKeyType.HANDLE,
|
||||
category: EventMetadataCategories.SOCIAL,
|
||||
},
|
||||
{
|
||||
icon: "mz:icon:fediverse",
|
||||
key: "mz:social:fediverse:account_url",
|
||||
label: t("Fediverse account") as string,
|
||||
description: t(
|
||||
"A fediverse account URL to follow for event updates"
|
||||
) as string,
|
||||
value: "",
|
||||
placeholder: "https://framapiaf.org/@mobilizon",
|
||||
type: EventMetadataType.STRING,
|
||||
keyType: EventMetadataKeyType.URL,
|
||||
category: EventMetadataCategories.SOCIAL,
|
||||
},
|
||||
{
|
||||
icon: "ticket-confirmation",
|
||||
key: "mz:ticket:external_url",
|
||||
label: t("Online ticketing") as string,
|
||||
description: t("An URL to an external ticketing platform") as string,
|
||||
value: "",
|
||||
type: EventMetadataType.STRING,
|
||||
keyType: EventMetadataKeyType.URL,
|
||||
category: EventMetadataCategories.BOOKING,
|
||||
},
|
||||
{
|
||||
icon: "cash",
|
||||
key: "mz:ticket:price_url",
|
||||
label: t("Price sheet") as string,
|
||||
description: t("A link to a page presenting the price options") as string,
|
||||
value: "",
|
||||
type: EventMetadataType.STRING,
|
||||
keyType: EventMetadataKeyType.URL,
|
||||
category: EventMetadataCategories.DETAILS,
|
||||
},
|
||||
{
|
||||
icon: "calendar-text",
|
||||
key: "mz:schedule_url",
|
||||
label: t("Schedule") as string,
|
||||
description: t("A link to a page presenting the event schedule") as string,
|
||||
value: "",
|
||||
type: EventMetadataType.STRING,
|
||||
keyType: EventMetadataKeyType.URL,
|
||||
category: EventMetadataCategories.DETAILS,
|
||||
},
|
||||
{
|
||||
icon: "webcam",
|
||||
key: "mz:visio:jitsi_meet",
|
||||
label: t("Jitsi Meet") as string,
|
||||
description: t("The Jitsi Meet video teleconference URL") as string,
|
||||
value: "",
|
||||
type: EventMetadataType.STRING,
|
||||
keyType: EventMetadataKeyType.URL,
|
||||
category: EventMetadataCategories.VIDEO_CONFERENCE,
|
||||
placeholder: "https://meet.jit.si/AFewWords",
|
||||
},
|
||||
{
|
||||
icon: "webcam",
|
||||
key: "mz:visio:zoom",
|
||||
label: t("Zoom") as string,
|
||||
description: t("The Zoom video teleconference URL") as string,
|
||||
value: "",
|
||||
type: EventMetadataType.STRING,
|
||||
keyType: EventMetadataKeyType.URL,
|
||||
category: EventMetadataCategories.VIDEO_CONFERENCE,
|
||||
pattern: /https:\/\/.*\.?zoom.us\/.*/,
|
||||
},
|
||||
{
|
||||
icon: "microsoft-teams",
|
||||
key: "mz:visio:microsoft_teams",
|
||||
label: t("Microsoft Teams") as string,
|
||||
description: t("The Microsoft Teams video teleconference URL") as string,
|
||||
value: "",
|
||||
type: EventMetadataType.STRING,
|
||||
keyType: EventMetadataKeyType.URL,
|
||||
category: EventMetadataCategories.VIDEO_CONFERENCE,
|
||||
pattern: /https:\/\/teams\.live\.com\/meet\/.+/,
|
||||
},
|
||||
{
|
||||
icon: "google-hangouts",
|
||||
key: "mz:visio:google_meet",
|
||||
label: t("Google Meet") as string,
|
||||
description: t("The Google Meet video teleconference URL") as string,
|
||||
value: "",
|
||||
type: EventMetadataType.STRING,
|
||||
keyType: EventMetadataKeyType.URL,
|
||||
category: EventMetadataCategories.VIDEO_CONFERENCE,
|
||||
pattern: /https:\/\/meet\.google\.com\/.+/,
|
||||
},
|
||||
{
|
||||
icon: "webcam",
|
||||
key: "mz:visio:big_blue_button",
|
||||
label: t("Big Blue Button") as string,
|
||||
description: t("The Big Blue Button video teleconference URL") as string,
|
||||
value: "",
|
||||
type: EventMetadataType.STRING,
|
||||
keyType: EventMetadataKeyType.URL,
|
||||
category: EventMetadataCategories.VIDEO_CONFERENCE,
|
||||
},
|
||||
];
|
||||
60
src/services/push-subscription.ts
Normal file
60
src/services/push-subscription.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { apolloClient } from "@/vue-apollo";
|
||||
import { provideApolloClient, useQuery } from "@vue/apollo-composable";
|
||||
import { WEB_PUSH } from "../graphql/config";
|
||||
import { IConfig } from "../types/config.model";
|
||||
|
||||
function urlBase64ToUint8Array(base64String: string): Uint8Array {
|
||||
const padding = "=".repeat((4 - (base64String.length % 4)) % 4);
|
||||
const base64 = (base64String + padding).replace(/-/g, "+").replace(/_/g, "/");
|
||||
|
||||
const rawData = window.atob(base64);
|
||||
const outputArray = new Uint8Array(rawData.length);
|
||||
|
||||
for (let i = 0; i < rawData.length; ++i) {
|
||||
outputArray[i] = rawData.charCodeAt(i);
|
||||
}
|
||||
return outputArray;
|
||||
}
|
||||
|
||||
export async function subscribeUserToPush(): Promise<PushSubscription | null> {
|
||||
const { onResult } = provideApolloClient(apolloClient)(() =>
|
||||
useQuery<{ config: IConfig }>(WEB_PUSH)
|
||||
);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
onResult(async ({ data }) => {
|
||||
if (data?.config?.webPush?.enabled && data?.config?.webPush?.publicKey) {
|
||||
const subscribeOptions = {
|
||||
userVisibleOnly: true,
|
||||
applicationServerKey: urlBase64ToUint8Array(
|
||||
data?.config?.webPush?.publicKey
|
||||
),
|
||||
};
|
||||
const registration = await navigator.serviceWorker.ready;
|
||||
try {
|
||||
const pushSubscription =
|
||||
await registration.pushManager.subscribe(subscribeOptions);
|
||||
console.debug("Received PushSubscription: ", pushSubscription);
|
||||
resolve(pushSubscription);
|
||||
} catch (e) {
|
||||
console.error("Error while subscribing to push notifications", e);
|
||||
}
|
||||
}
|
||||
reject(null);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export async function unsubscribeUserToPush(): Promise<string | undefined> {
|
||||
console.debug("performing unsubscribeUserToPush");
|
||||
const registration = await navigator.serviceWorker.ready;
|
||||
console.debug("found registration", registration);
|
||||
const subscription = await registration.pushManager?.getSubscription();
|
||||
console.debug("found subscription", subscription);
|
||||
if (subscription && (await subscription?.unsubscribe()) === true) {
|
||||
console.debug("done unsubscription");
|
||||
return subscription?.endpoint;
|
||||
}
|
||||
console.debug("went wrong");
|
||||
return undefined;
|
||||
}
|
||||
58
src/services/statistics/index.ts
Normal file
58
src/services/statistics/index.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { IAnalyticsConfig, IKeyValueConfig } from "@/types/config.model";
|
||||
|
||||
let app: any = null;
|
||||
|
||||
export const setAppForAnalytics = (newApp: any) => {
|
||||
app = newApp;
|
||||
};
|
||||
|
||||
export const statistics = async (
|
||||
configAnalytics: IAnalyticsConfig[],
|
||||
environement: any
|
||||
) => {
|
||||
console.debug("Loading statistics", configAnalytics);
|
||||
const matomoConfig = checkProviderConfig(configAnalytics, "matomo");
|
||||
if (matomoConfig?.enabled === true) {
|
||||
const { matomo } = (await import("./matomo")) as any;
|
||||
matomo({ ...environement, app }, convertConfig(matomoConfig.configuration));
|
||||
}
|
||||
|
||||
const sentryConfig = checkProviderConfig(configAnalytics, "sentry");
|
||||
if (sentryConfig?.enabled === true) {
|
||||
const { sentry } = (await import("./sentry")) as any;
|
||||
sentry({ ...environement, app }, convertConfig(sentryConfig.configuration));
|
||||
}
|
||||
};
|
||||
|
||||
export const checkProviderConfig = (
|
||||
configAnalytics: IAnalyticsConfig[],
|
||||
providerName: string
|
||||
): IAnalyticsConfig | undefined => {
|
||||
return configAnalytics?.find((provider) => provider.id === providerName);
|
||||
};
|
||||
|
||||
export const convertConfig = (
|
||||
configs: IKeyValueConfig[]
|
||||
): Record<string, any> => {
|
||||
return configs.reduce(
|
||||
(acc, config) => {
|
||||
acc[config.key] = toType(config.value, config.type);
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, any>
|
||||
);
|
||||
};
|
||||
|
||||
const toType = (value: string, type: string): string | number | boolean => {
|
||||
switch (type) {
|
||||
case "boolean":
|
||||
return value === "true";
|
||||
case "integer":
|
||||
return parseInt(value, 10);
|
||||
case "float":
|
||||
return parseFloat(value);
|
||||
case "string":
|
||||
default:
|
||||
return value;
|
||||
}
|
||||
};
|
||||
14
src/services/statistics/matomo.ts
Normal file
14
src/services/statistics/matomo.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import VueMatomo from "vue-matomo";
|
||||
|
||||
export const matomo = (environment: any, matomoConfiguration: any) => {
|
||||
console.debug("Loading Matomo statistics");
|
||||
console.debug(
|
||||
"Calling VueMatomo with the following configuration",
|
||||
matomoConfiguration
|
||||
);
|
||||
environment.app.use(VueMatomo, {
|
||||
...matomoConfiguration,
|
||||
router: environment.router,
|
||||
debug: import.meta.env.DEV,
|
||||
});
|
||||
};
|
||||
9
src/services/statistics/plausible.ts
Normal file
9
src/services/statistics/plausible.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { VuePlausible } from "vue-plausible";
|
||||
export default (environment: any, plausibleConfiguration: any) => {
|
||||
console.debug("Loading Plausible statistics");
|
||||
|
||||
environment.app.use(VuePlausible, {
|
||||
// see configuration section
|
||||
...plausibleConfiguration,
|
||||
});
|
||||
};
|
||||
54
src/services/statistics/sentry.ts
Normal file
54
src/services/statistics/sentry.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import * as Sentry from "@sentry/vue";
|
||||
import { Integrations } from "@sentry/tracing";
|
||||
|
||||
export const sentry = (environment: any, sentryConfiguration: any) => {
|
||||
console.debug("Loading Sentry statistics");
|
||||
console.debug(
|
||||
"Calling Sentry with the following configuration",
|
||||
sentryConfiguration
|
||||
);
|
||||
// Don't attach errors to previous events
|
||||
window.sessionStorage.removeItem("lastEventId");
|
||||
Sentry.init({
|
||||
app: environment.app,
|
||||
dsn: sentryConfiguration.dsn,
|
||||
debug: import.meta.env.DEV,
|
||||
integrations: [
|
||||
new Integrations.BrowserTracing({
|
||||
routingInstrumentation: Sentry.vueRouterInstrumentation(
|
||||
environment.router
|
||||
),
|
||||
tracingOrigins: [window.origin, /^\//],
|
||||
}),
|
||||
],
|
||||
beforeSend(event) {
|
||||
// Check if it is an exception, and if so, save it in session storage
|
||||
// so that it can be retreived from the error component
|
||||
if (event.exception && event.event_id) {
|
||||
window.sessionStorage.setItem("lastEventId", event.event_id);
|
||||
}
|
||||
return event;
|
||||
},
|
||||
// Set tracesSampleRate to 1.0 to capture 100%
|
||||
// of transactions for performance monitoring.
|
||||
// We recommend adjusting this value in production
|
||||
tracesSampleRate: Number.parseFloat(sentryConfiguration.tracesSampleRate),
|
||||
release: environment.version,
|
||||
logErrors: true,
|
||||
});
|
||||
};
|
||||
|
||||
export const submitFeedback = async (
|
||||
endpoint: string,
|
||||
dsn: string,
|
||||
params: Record<string, string>
|
||||
): Promise<void> => {
|
||||
await fetch(endpoint, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-type": "application/json",
|
||||
Authorization: `DSN ${dsn}`,
|
||||
},
|
||||
body: JSON.stringify(params),
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user