From df239ba436524ccc8fd04a359260b5c7a9f8c375 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carl-Gerhard=20Lindesva=CC=88rd?= Date: Sun, 21 Jan 2024 22:50:49 +0100 Subject: [PATCH] make tracker script smaller and general improvement for web --- .gitignore | 1 + .../[projectId]/list-dashboards.tsx | 9 +-- .../(app)/[organizationId]/list-projects.tsx | 6 +- .../settings/clients/list-clients.tsx | 7 +- .../settings/projects/list-projects.tsx | 6 +- apps/web/src/app/(app)/layout-menu.tsx | 12 ++-- .../(app)/layout-organization-selector.tsx | 4 +- .../report/PreviousDiffIndicator.tsx | 7 +- .../components/report/ReportSaveButton.tsx | 5 +- .../report/sidebar/ReportBreakdowns.tsx | 6 +- .../report/sidebar/ReportEventFilters.tsx | 5 +- .../report/sidebar/ReportEvents.tsx | 6 +- apps/web/src/modals/SaveReport.tsx | 13 ++-- apps/web/src/server/api/routers/chart.ts | 14 +++- apps/worker/src/jobs/events.ts | 7 ++ apps/worker/src/utils/user-agent.ts | 64 +++++++++++++++++++ packages/sdk-web/index.ts | 13 +--- packages/sdk-web/src/utils.ts | 59 ----------------- 18 files changed, 132 insertions(+), 112 deletions(-) create mode 100644 apps/worker/src/utils/user-agent.ts diff --git a/.gitignore b/.gitignore index 8303cd42..233e84e5 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ packages/sdk/profileId.txt packages/sdk/test.ts dump.sql +dump-*.sql # Logs diff --git a/apps/web/src/app/(app)/[organizationId]/[projectId]/list-dashboards.tsx b/apps/web/src/app/(app)/[organizationId]/[projectId]/list-dashboards.tsx index c1c78023..47ef23e7 100644 --- a/apps/web/src/app/(app)/[organizationId]/[projectId]/list-dashboards.tsx +++ b/apps/web/src/app/(app)/[organizationId]/[projectId]/list-dashboards.tsx @@ -1,14 +1,15 @@ 'use client'; -import { api, handleError, handleErrorToastOptions } from '@/app/_trpc/client'; +import { api, handleErrorToastOptions } from '@/app/_trpc/client'; import { Card, CardActions, CardActionsItem } from '@/components/Card'; import { ToastAction } from '@/components/ui/toast'; import { toast } from '@/components/ui/use-toast'; +import { useAppParams } from '@/hooks/useAppParams'; import { pushModal } from '@/modals'; import type { getDashboardsByProjectId } from '@/server/services/dashboard.service'; -import { Pencil, Plus, Trash } from 'lucide-react'; +import { Pencil, Trash } from 'lucide-react'; import Link from 'next/link'; -import { useParams, useRouter } from 'next/navigation'; +import { useRouter } from 'next/navigation'; interface ListDashboardsProps { dashboards: Awaited>; @@ -16,7 +17,7 @@ interface ListDashboardsProps { export function ListDashboards({ dashboards }: ListDashboardsProps) { const router = useRouter(); - const params = useParams(); + const params = useAppParams(); const { organizationId, projectId } = params; const deletion = api.dashboard.delete.useMutation({ onError: (error, variables) => { diff --git a/apps/web/src/app/(app)/[organizationId]/list-projects.tsx b/apps/web/src/app/(app)/[organizationId]/list-projects.tsx index 4817c8ed..710188c4 100644 --- a/apps/web/src/app/(app)/[organizationId]/list-projects.tsx +++ b/apps/web/src/app/(app)/[organizationId]/list-projects.tsx @@ -1,19 +1,19 @@ 'use client'; import { Card } from '@/components/Card'; +import { useAppParams } from '@/hooks/useAppParams'; import { pushModal } from '@/modals'; import type { getProjectsByOrganizationId } from '@/server/services/project.service'; import { Plus } from 'lucide-react'; import Link from 'next/link'; -import { useParams } from 'next/navigation'; interface ListProjectsProps { projects: Awaited>; } export function ListProjects({ projects }: ListProjectsProps) { - const params = useParams(); - const organizationId = params.organizationId as string; + const params = useAppParams(); + const organizationId = params.organizationId; return ( <> diff --git a/apps/web/src/app/(app)/[organizationId]/settings/clients/list-clients.tsx b/apps/web/src/app/(app)/[organizationId]/settings/clients/list-clients.tsx index fb2d18f5..ede7b17a 100644 --- a/apps/web/src/app/(app)/[organizationId]/settings/clients/list-clients.tsx +++ b/apps/web/src/app/(app)/[organizationId]/settings/clients/list-clients.tsx @@ -2,19 +2,18 @@ import { StickyBelowHeader } from '@/app/(app)/layout-sticky-below-header'; import { columns } from '@/components/clients/table'; -import { ContentHeader } from '@/components/Content'; import { DataTable } from '@/components/DataTable'; import { Button } from '@/components/ui/button'; +import { useAppParams } from '@/hooks/useAppParams'; import { pushModal } from '@/modals'; import type { getClientsByOrganizationId } from '@/server/services/clients.service'; -import { KeySquareIcon, PlusIcon } from 'lucide-react'; -import { useParams } from 'next/navigation'; +import { PlusIcon } from 'lucide-react'; interface ListClientsProps { clients: Awaited>; } export default function ListClients({ clients }: ListClientsProps) { - const organizationId = useParams().organizationId as string; + const organizationId = useAppParams().organizationId; return ( <> diff --git a/apps/web/src/app/(app)/[organizationId]/settings/projects/list-projects.tsx b/apps/web/src/app/(app)/[organizationId]/settings/projects/list-projects.tsx index 78b7515d..81f865ff 100644 --- a/apps/web/src/app/(app)/[organizationId]/settings/projects/list-projects.tsx +++ b/apps/web/src/app/(app)/[organizationId]/settings/projects/list-projects.tsx @@ -4,16 +4,16 @@ import { StickyBelowHeader } from '@/app/(app)/layout-sticky-below-header'; import { DataTable } from '@/components/DataTable'; import { columns } from '@/components/projects/table'; import { Button } from '@/components/ui/button'; +import { useAppParams } from '@/hooks/useAppParams'; import { pushModal } from '@/modals'; import type { getProjectsByOrganizationId } from '@/server/services/project.service'; -import { PlusIcon, WarehouseIcon } from 'lucide-react'; -import { useParams } from 'next/navigation'; +import { PlusIcon } from 'lucide-react'; interface ListProjectsProps { projects: Awaited>; } export default function ListProjects({ projects }: ListProjectsProps) { - const organizationId = useParams().organizationId as string; + const organizationId = useAppParams().organizationId; return ( <> diff --git a/apps/web/src/app/(app)/layout-menu.tsx b/apps/web/src/app/(app)/layout-menu.tsx index 4d6eeb8c..16842d80 100644 --- a/apps/web/src/app/(app)/layout-menu.tsx +++ b/apps/web/src/app/(app)/layout-menu.tsx @@ -1,5 +1,6 @@ 'use client'; +import { useAppParams } from '@/hooks/useAppParams'; import type { IServiceRecentDashboards } from '@/server/services/dashboard.service'; import { BuildingIcon, @@ -13,7 +14,7 @@ import { } from 'lucide-react'; import type { LucideProps } from 'lucide-react'; import Link from 'next/link'; -import { useParams, usePathname } from 'next/navigation'; +import { usePathname } from 'next/navigation'; function LinkWithIcon({ href, @@ -44,12 +45,11 @@ export default function LayoutMenu({ fallbackProjectId, }: LayoutMenuProps) { const pathname = usePathname(); - const params = useParams(); - const projectId = ( + const params = useAppParams(); + const projectId = !params.projectId || params.projectId === 'undefined' ? fallbackProjectId - : params.projectId - ) as string | null; + : params.projectId; return ( <> @@ -73,7 +73,7 @@ export default function LayoutMenu({ label="Settings" href={`/${params.organizationId}/settings/organization`} /> - {pathname.includes('/settings/') && ( + {pathname?.includes('/settings/') && (
item.id === params.organizationId diff --git a/apps/web/src/components/report/PreviousDiffIndicator.tsx b/apps/web/src/components/report/PreviousDiffIndicator.tsx index 07d520d5..bd80a312 100644 --- a/apps/web/src/components/report/PreviousDiffIndicator.tsx +++ b/apps/web/src/components/report/PreviousDiffIndicator.tsx @@ -17,11 +17,8 @@ export function PreviousDiffIndicator({ }: PreviousDiffIndicatorProps) { const { previous } = useChartContext(); const number = useNumber(); - if ( - (children === undefined && (diff === null || diff === undefined)) || - previous === false - ) { - return null; + if (diff === null || diff === undefined || previous === false) { + return children ?? null; } return ( diff --git a/apps/web/src/components/report/ReportSaveButton.tsx b/apps/web/src/components/report/ReportSaveButton.tsx index 4a9b5b49..6f5c9e86 100644 --- a/apps/web/src/components/report/ReportSaveButton.tsx +++ b/apps/web/src/components/report/ReportSaveButton.tsx @@ -3,6 +3,7 @@ import { api, handleError } from '@/app/_trpc/client'; import { Button } from '@/components/ui/button'; import { toast } from '@/components/ui/use-toast'; +import { useAppParams } from '@/hooks/useAppParams'; import { pushModal } from '@/modals'; import { useDispatch, useSelector } from '@/redux'; import { SaveIcon } from 'lucide-react'; @@ -14,7 +15,7 @@ interface ReportSaveButtonProps { className?: string; } export function ReportSaveButton({ className }: ReportSaveButtonProps) { - const { reportId } = useParams(); + const { reportId } = useAppParams<{ reportId: string | undefined }>(); const dispatch = useDispatch(); const update = api.report.update.useMutation({ onSuccess() { @@ -36,7 +37,7 @@ export function ReportSaveButton({ className }: ReportSaveButtonProps) { loading={update.isLoading} onClick={() => { update.mutate({ - reportId: reportId as string, + reportId: reportId, report, }); }} diff --git a/apps/web/src/components/report/sidebar/ReportBreakdowns.tsx b/apps/web/src/components/report/sidebar/ReportBreakdowns.tsx index 9d0e77ea..bce0e42a 100644 --- a/apps/web/src/components/report/sidebar/ReportBreakdowns.tsx +++ b/apps/web/src/components/report/sidebar/ReportBreakdowns.tsx @@ -3,20 +3,20 @@ import { api } from '@/app/_trpc/client'; import { ColorSquare } from '@/components/ColorSquare'; import { Combobox } from '@/components/ui/combobox'; +import { useAppParams } from '@/hooks/useAppParams'; import { useDispatch, useSelector } from '@/redux'; import type { IChartBreakdown } from '@/types'; -import { useParams } from 'next/navigation'; import { addBreakdown, changeBreakdown, removeBreakdown } from '../reportSlice'; import { ReportBreakdownMore } from './ReportBreakdownMore'; import type { ReportEventMoreProps } from './ReportEventMore'; export function ReportBreakdowns() { - const params = useParams(); + const params = useAppParams(); const selectedBreakdowns = useSelector((state) => state.report.breakdowns); const dispatch = useDispatch(); const propertiesQuery = api.chart.properties.useQuery({ - projectId: params.projectId as string, + projectId: params.projectId, }); const propertiesCombobox = (propertiesQuery.data ?? []).map((item) => ({ value: item, diff --git a/apps/web/src/components/report/sidebar/ReportEventFilters.tsx b/apps/web/src/components/report/sidebar/ReportEventFilters.tsx index 962eb6da..1259be2d 100644 --- a/apps/web/src/components/report/sidebar/ReportEventFilters.tsx +++ b/apps/web/src/components/report/sidebar/ReportEventFilters.tsx @@ -14,6 +14,7 @@ import { CommandSeparator, } from '@/components/ui/command'; import { RenderDots } from '@/components/ui/RenderDots'; +import { useAppParams } from '@/hooks/useAppParams'; import { useMappings } from '@/hooks/useMappings'; import { useDispatch } from '@/redux'; import type { @@ -38,12 +39,12 @@ export function ReportEventFilters({ isCreating, setIsCreating, }: ReportEventFiltersProps) { - const params = useParams(); + const params = useAppParams(); const dispatch = useDispatch(); const propertiesQuery = api.chart.properties.useQuery( { event: event.name, - projectId: params.projectId as string, + projectId: params.projectId, }, { enabled: !!event.name, diff --git a/apps/web/src/components/report/sidebar/ReportEvents.tsx b/apps/web/src/components/report/sidebar/ReportEvents.tsx index ebf46f06..6ac39b72 100644 --- a/apps/web/src/components/report/sidebar/ReportEvents.tsx +++ b/apps/web/src/components/report/sidebar/ReportEvents.tsx @@ -6,6 +6,7 @@ import { ColorSquare } from '@/components/ColorSquare'; import { Dropdown } from '@/components/Dropdown'; import { Combobox } from '@/components/ui/combobox'; import { Input } from '@/components/ui/input'; +import { useAppParams } from '@/hooks/useAppParams'; import { useDebounceFn } from '@/hooks/useDebounceFn'; import { useDispatch, useSelector } from '@/redux'; import type { IChartEvent } from '@/types'; @@ -21,9 +22,9 @@ export function ReportEvents() { const [isCreating, setIsCreating] = useState(false); const selectedEvents = useSelector((state) => state.report.events); const dispatch = useDispatch(); - const params = useParams(); + const params = useAppParams(); const eventsQuery = api.chart.events.useQuery({ - projectId: String(params.projectId), + projectId: params.projectId, }); const eventsCombobox = (eventsQuery.data ?? []).map((item) => ({ value: item.name, @@ -157,6 +158,7 @@ export function ReportEvents() { { dispatch( addEvent({ diff --git a/apps/web/src/modals/SaveReport.tsx b/apps/web/src/modals/SaveReport.tsx index 3e4b7f2f..178b571f 100644 --- a/apps/web/src/modals/SaveReport.tsx +++ b/apps/web/src/modals/SaveReport.tsx @@ -7,9 +7,10 @@ import { Button } from '@/components/ui/button'; import { Combobox } from '@/components/ui/combobox'; import { Label } from '@/components/ui/label'; import { toast } from '@/components/ui/use-toast'; +import { useAppParams } from '@/hooks/useAppParams'; import type { IChartInput } from '@/types'; import { zodResolver } from '@hookform/resolvers/zod'; -import { useParams, useRouter, useSearchParams } from 'next/navigation'; +import { useRouter, useSearchParams } from 'next/navigation'; import { Controller, useForm } from 'react-hook-form'; import { z } from 'zod'; @@ -30,11 +31,11 @@ type IForm = z.infer; export default function SaveReport({ report }: SaveReportProps) { const router = useRouter(); - const params = useParams(); - const organizationId = params.organizationId as string; - const projectId = params.projectId as string; + const params = useAppParams(); + const organizationId = params.organizationId; + const projectId = params.projectId; const searchParams = useSearchParams(); - const dashboardId = searchParams.get('dashboardId') ?? undefined; + const dashboardId = searchParams?.get('dashboardId') ?? undefined; const save = api.report.save.useMutation({ onError: handleError, @@ -47,7 +48,7 @@ export default function SaveReport({ report }: SaveReportProps) { router.push( `/${organizationId}/${projectId}/reports/${ res.id - }?${searchParams.toString()}` + }?${searchParams?.toString()}` ); }, }); diff --git a/apps/web/src/server/api/routers/chart.ts b/apps/web/src/server/api/routers/chart.ts index bbb24a15..631f9759 100644 --- a/apps/web/src/server/api/routers/chart.ts +++ b/apps/web/src/server/api/routers/chart.ts @@ -45,6 +45,7 @@ export const chartRouter = createTRPCRouter({ () => db.event.findMany({ take: 500, + distinct: 'name', where: { project_id: projectId, ...(event @@ -124,6 +125,14 @@ export const chartRouter = createTRPCRouter({ let diff = 0; switch (input.range) { + case '30min': { + diff = 1000 * 60 * 30; + break; + } + case '1h': { + diff = 1000 * 60 * 60; + break; + } case '24h': case 'today': { diff = 1000 * 60 * 60 * 24; @@ -243,7 +252,10 @@ function getPreviousDataDiff(current: number, previous: number | undefined) { ); return { - diff: Number.isNaN(diff) || !Number.isFinite(diff) ? null : diff, + diff: + Number.isNaN(diff) || !Number.isFinite(diff) || current === previous + ? null + : diff, state: current > previous ? 'positive' diff --git a/apps/worker/src/jobs/events.ts b/apps/worker/src/jobs/events.ts index ad9f227b..76ea7e52 100644 --- a/apps/worker/src/jobs/events.ts +++ b/apps/worker/src/jobs/events.ts @@ -1,3 +1,4 @@ +import { getDevice, getOS } from '@/utils/user-agent'; import type { Job } from 'bullmq'; import { mergeDeepRight } from 'ramda'; @@ -152,6 +153,12 @@ export async function eventsJob(job: Job) { break; } case 'event': { + const userAgent = payload.properties.ua as string | undefined; + if (userAgent) { + payload.properties.device = getDevice(userAgent); + payload.properties.os = getOS(userAgent); + delete payload.properties.ua; + } await db.event.create({ data: { name: payload.name, diff --git a/apps/worker/src/utils/user-agent.ts b/apps/worker/src/utils/user-agent.ts new file mode 100644 index 00000000..ec95b85d --- /dev/null +++ b/apps/worker/src/utils/user-agent.ts @@ -0,0 +1,64 @@ +export function getOS(ua?: string) { + if (!ua) { + return null; + } + if (/iPad/i.test(ua)) { + return 'iPad'; + } + if (/iPhone/i.test(ua)) { + return 'iPhone'; + } + if (/iPod/i.test(ua)) { + return 'iPod'; + } + if (/Macintosh/i.test(ua)) { + return 'macOS'; + } + if (/IEMobile|Windows/i.test(ua)) { + return 'Windows'; + } + if (/Android/i.test(ua)) { + return 'Android'; + } + if (/BlackBerry/i.test(ua)) { + return 'BlackBerry'; + } + if (/EF500/i.test(ua)) { + return 'Bluebird'; + } + if (/CrOS/i.test(ua)) { + return 'Chrome OS'; + } + if (/DL-AXIS/i.test(ua)) { + return 'Datalogic'; + } + if (/CT50/i.test(ua)) { + return 'Honeywell'; + } + if (/TC70|TC55/i.test(ua)) { + return 'Zebra'; + } + if (/Linux/i.test(ua)) { + return 'Generic Linux'; + } + return 'Unknown'; +} + +export function getDevice(ua?: string) { + if (!ua) { + return null; + } + + const t1 = + /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test( + ua + ); + const t2 = + /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw-(n|u)|c55\/|capi|ccwa|cdm-|cell|chtm|cldc|cmd-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc-s|devi|dica|dmob|do(c|p)o|ds(12|-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(-|_)|g1 u|g560|gene|gf-5|g-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd-(m|p|t)|hei-|hi(pt|ta)|hp( i|ip)|hs-c|ht(c(-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i-(20|go|ma)|i230|iac( |-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|-[a-w])|libw|lynx|m1-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|-([1-8]|c))|phil|pire|pl(ay|uc)|pn-2|po(ck|rt|se)|prox|psio|pt-g|qa-a|qc(07|12|21|32|60|-[2-7]|i-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h-|oo|p-)|sdk\/|se(c(-|0|1)|47|mc|nd|ri)|sgh-|shar|sie(-|m)|sk-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h-|v-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl-|tdg-|tel(i|m)|tim-|t-mo|to(pl|sh)|ts(70|m-|m3|m5)|tx-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas-|your|zeto|zte-/i.test( + ua.slice(0, 4) + ); + if (t1 || t2) { + return 'mobile'; + } + return 'desktop'; +} diff --git a/packages/sdk-web/index.ts b/packages/sdk-web/index.ts index fb5e2515..3a206261 100644 --- a/packages/sdk-web/index.ts +++ b/packages/sdk-web/index.ts @@ -3,7 +3,7 @@ import { Mixan } from '@mixan/sdk'; import type { PartialBy } from '@mixan/types'; import { parseQuery } from './src/parseQuery'; -import { getDevice, getOS, getTimezone } from './src/utils'; +import { getTimezone } from './src/utils'; export class MixanWeb extends Mixan { constructor( @@ -29,16 +29,12 @@ export class MixanWeb extends Mixan { private parseUrl(url?: string) { if (!url || url === '') { - return { - direct: true, - }; + return {}; } const ref = new URL(url); return { host: ref.host, - hostname: ref.hostname, - url: ref.href, path: ref.pathname, query: parseQuery(ref.search), hash: ref.hash, @@ -47,16 +43,13 @@ export class MixanWeb extends Mixan { private properties() { return { - os: getOS(), - device: getDevice(), ua: navigator.userAgent, - referrer: this.parseUrl(document.referrer), + referrer: document.referrer || undefined, language: navigator.language, timezone: getTimezone(), screen: { width: window.screen.width, height: window.screen.height, - pixelRatio: window.devicePixelRatio, }, title: document.title, ...this.parseUrl(window.location.href), diff --git a/packages/sdk-web/src/utils.ts b/packages/sdk-web/src/utils.ts index a6f5100d..7fd02a16 100644 --- a/packages/sdk-web/src/utils.ts +++ b/packages/sdk-web/src/utils.ts @@ -1,62 +1,3 @@ -export function getOS() { - if (/iPad/i.test(navigator.userAgent)) { - return 'iPad'; - } - if (/iPhone/i.test(navigator.userAgent)) { - return 'iPhone'; - } - if (/iPod/i.test(navigator.userAgent)) { - return 'iPod'; - } - if (/Macintosh/i.test(navigator.userAgent)) { - return 'macOS'; - } - if (/IEMobile|Windows/i.test(navigator.userAgent)) { - return 'Windows'; - } - if (/Android/i.test(navigator.userAgent)) { - return 'Android'; - } - if (/BlackBerry/i.test(navigator.userAgent)) { - return 'BlackBerry'; - } - if (/EF500/i.test(navigator.userAgent)) { - return 'Bluebird'; - } - if (/CrOS/i.test(navigator.userAgent)) { - return 'Chrome OS'; - } - if (/DL-AXIS/i.test(navigator.userAgent)) { - return 'Datalogic'; - } - if (/CT50/i.test(navigator.userAgent)) { - return 'Honeywell'; - } - if (/TC70|TC55/i.test(navigator.userAgent)) { - return 'Zebra'; - } - if (/Linux/i.test(navigator.userAgent)) { - return 'Generic Linux'; - } - return 'Unknown'; -} - -export function getDevice() { - const ua = navigator.userAgent; - const t1 = - /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test( - ua - ); - const t2 = - /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw-(n|u)|c55\/|capi|ccwa|cdm-|cell|chtm|cldc|cmd-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc-s|devi|dica|dmob|do(c|p)o|ds(12|-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(-|_)|g1 u|g560|gene|gf-5|g-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd-(m|p|t)|hei-|hi(pt|ta)|hp( i|ip)|hs-c|ht(c(-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i-(20|go|ma)|i230|iac( |-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|-[a-w])|libw|lynx|m1-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|-([1-8]|c))|phil|pire|pl(ay|uc)|pn-2|po(ck|rt|se)|prox|psio|pt-g|qa-a|qc(07|12|21|32|60|-[2-7]|i-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h-|oo|p-)|sdk\/|se(c(-|0|1)|47|mc|nd|ri)|sgh-|shar|sie(-|m)|sk-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h-|v-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl-|tdg-|tel(i|m)|tim-|t-mo|to(pl|sh)|ts(70|m-|m3|m5)|tx-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas-|your|zeto|zte-/i.test( - ua.slice(0, 4) - ); - if (t1 || t2) { - return 'mobile'; - } - return 'desktop'; -} - export function getTimezone() { try { return Intl.DateTimeFormat().resolvedOptions().timeZone;