diff --git a/package.json b/package.json index 66375c48..e0459fea 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "private": true, - "version": "1.9.1", + "version": "1.9.2", "description": "All-In-One Remote Debugging Tool", "homepage": "https://huolalatech.github.io/page-spy-web", "repository": { @@ -50,10 +50,10 @@ }, "dependencies": { "@ant-design/icons": "^4.7.0", - "@huolala-tech/page-spy-browser": "^1.9.3", - "@huolala-tech/page-spy-plugin-data-harbor": "^1.3.3", - "@huolala-tech/page-spy-plugin-rrweb": "^1.2.3", - "@huolala-tech/page-spy-types": "^1.9.2", + "@huolala-tech/page-spy-browser": "^1.9.8", + "@huolala-tech/page-spy-plugin-data-harbor": "^1.3.7", + "@huolala-tech/page-spy-plugin-rrweb": "^1.2.7", + "@huolala-tech/page-spy-types": "^1.9.6", "@huolala-tech/react-json-view": "^1.2.5", "@huolala-tech/request": "^1.1.2", "@mdx-js/mdx": "^3.0.1", diff --git a/src/assets/image/android.svg b/src/assets/image/android.svg index 105919e4..8d874688 100644 --- a/src/assets/image/android.svg +++ b/src/assets/image/android.svg @@ -1 +1,11 @@ - + + + + + + + + + + + diff --git a/src/assets/image/chrome.svg b/src/assets/image/chrome.svg index 49c766cd..d9c3ba94 100644 --- a/src/assets/image/chrome.svg +++ b/src/assets/image/chrome.svg @@ -1,23 +1,7 @@ - - - - - - - - - - - - - - - + + + + + + diff --git a/src/assets/image/huawei-browser.svg b/src/assets/image/huawei-browser.svg new file mode 100644 index 00000000..b42bea7f --- /dev/null +++ b/src/assets/image/huawei-browser.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/assets/image/miniprogram.svg b/src/assets/image/miniprogram.svg index 4cd97651..7a230e6e 100644 --- a/src/assets/image/miniprogram.svg +++ b/src/assets/image/miniprogram.svg @@ -1,8 +1,11 @@ - - - - - + + + + + + + + + + diff --git a/src/assets/image/mp-dingtalk.svg b/src/assets/image/mp-dingtalk.svg index 77737145..bfe043d7 100644 --- a/src/assets/image/mp-dingtalk.svg +++ b/src/assets/image/mp-dingtalk.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/assets/image/react.svg b/src/assets/image/react.svg index 9da33f04..2b871aa2 100644 --- a/src/assets/image/react.svg +++ b/src/assets/image/react.svg @@ -1 +1,14 @@ - \ No newline at end of file + + + + + + + + + + + + + + diff --git a/src/assets/image/screenshot/v1.9.2-offline-log-size.png b/src/assets/image/screenshot/v1.9.2-offline-log-size.png new file mode 100644 index 00000000..1ae0e9f2 Binary files /dev/null and b/src/assets/image/screenshot/v1.9.2-offline-log-size.png differ diff --git a/src/assets/image/wechat.svg b/src/assets/image/wechat.svg index 2687d630..a8b42c56 100644 --- a/src/assets/image/wechat.svg +++ b/src/assets/image/wechat.svg @@ -1,6 +1,21 @@ - - - + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/image/windows.svg b/src/assets/image/windows.svg index c9dd889c..67ac61d9 100644 --- a/src/assets/image/windows.svg +++ b/src/assets/image/windows.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/components/DBTable/index.tsx b/src/components/DBTable/index.tsx index a7f4178d..7467c0e3 100644 --- a/src/components/DBTable/index.tsx +++ b/src/components/DBTable/index.tsx @@ -24,10 +24,20 @@ import { useTranslation } from 'react-i18next'; import './index.less'; import { useEventListener } from '@/utils/useEventListener'; import { CUSTOM_EVENT } from '@/store/socket-message/socket'; +import { ResizeCallbackData } from 'react-resizable'; +import { ResizableTitle } from '../ResizableTitle'; +import { ONLINE_DB_CACHE } from '../ResizableTitle/cache-key'; +import { fromPairs, map } from 'lodash-es'; +import { ColumnType } from 'antd/es/table/interface'; -const { Column } = Table; const { Option } = Select; +interface DBItem { + index: number; + keyPath: string; + value: string; +} + export const DBTable = () => { const { t: ct } = useTranslation('translation', { keyPrefix: 'common' }); const { t } = useTranslation('translation', { keyPrefix: 'storage' }); @@ -79,12 +89,79 @@ export const DBTable = () => { } }); + const [columns, setColumns] = useState[]>(() => { + const cache = localStorage.getItem(ONLINE_DB_CACHE); + const value = cache && JSON.parse(cache); + + return [ + { + dataIndex: 'index', + title: '#', + width: value?.index || 80, + }, + { + dataIndex: 'keyPath', + width: value?.keyPath || 300, + title: ( + + Key{' '} + {dbMsg?.store?.keyPath && ( + + (Key path: + + ) + + )} + + ), + render: (value: string) => ( + + ), + }, + { + dataIndex: 'value', + title: 'Value', + render: (value: string) => ( + + ), + }, + ]; + }); + + const mergedColumns = useMemo(() => { + return columns.map((c, index) => ({ + ...c, + onHeaderCell: () => ({ + width: c.width, + onResize: (( + _: React.SyntheticEvent, + { size }: ResizeCallbackData, + ) => { + const newCols = [...columns]; + newCols[index] = { + ...newCols[index], + width: size.width, + }; + setColumns(newCols); + }) as React.ReactEventHandler, + onResizeStop: () => { + const value = fromPairs(map(columns, (c) => [c.dataIndex, c.width])); + localStorage.setItem(ONLINE_DB_CACHE, JSON.stringify(value)); + }, + }), + })); + }, [columns]); + const tableData = useMemo(() => { if (!dbMsg) return []; const { page, data } = dbMsg; const result = data.map((val: any, idx: number) => { const index = 50 * (page.current - 1) + idx; - const item: any = { + const item = { index, keyPath: val.key, value: val.value, @@ -115,6 +192,7 @@ export const DBTable = () => { onChange={() => { form.setFieldValue('store', undefined); }} + optionLabelProp="name" > {basicInfo?.map((i) => { return ( @@ -247,37 +325,16 @@ export const DBTable = () => { rowKey="index" bordered dataSource={tableData} + columns={mergedColumns} pagination={false} tableLayout="fixed" size="small" - > - - - Key{' '} - {dbMsg?.store?.keyPath && ( - - (Key path: - - ) - - )} - - } - render={(value) => } - dataIndex="keyPath" - /> - } - /> - + components={{ + header: { + cell: ResizableTitle, + }, + }} + />

{t('total-entries')}: {dbMsg?.total || 0}

diff --git a/src/components/NetworkTable/NetworkDetail/index.tsx b/src/components/NetworkTable/NetworkDetail/index.tsx index fc4cfaf8..b59fc1c5 100644 --- a/src/components/NetworkTable/NetworkDetail/index.tsx +++ b/src/components/NetworkTable/NetworkDetail/index.tsx @@ -1,6 +1,5 @@ import { EntriesBody } from '@/components/EntriesBody'; -import { getObjectKeys } from '@/utils'; -import { SpyNetwork } from '@huolala-tech/page-spy-types'; +import { getObjectKeys, ResolvedNetworkInfo } from '@/utils'; import { Empty, Space } from 'antd'; import { ReactNode, memo, useEffect, useMemo, useState } from 'react'; import { PartOfHeader } from '../PartOfHeader'; @@ -14,7 +13,7 @@ import Icon from '@ant-design/icons'; import { ReactComponent as CloseSvg } from '@/assets/image/close.svg'; interface Props { - data: SpyNetwork.RequestInfo; + data: ResolvedNetworkInfo; onClose: () => void; } @@ -25,8 +24,8 @@ const generalFieldMap = { interface TabItem { title: string; - visible: (data: SpyNetwork.RequestInfo) => boolean; - content: (data: SpyNetwork.RequestInfo) => ReactNode; + visible: (data: ResolvedNetworkInfo) => boolean; + content: (data: ResolvedNetworkInfo) => ReactNode; } const TABS: TabItem[] = [ diff --git a/src/components/NetworkTable/ResponseBody/index.tsx b/src/components/NetworkTable/ResponseBody/index.tsx index 63014272..30b0b364 100644 --- a/src/components/NetworkTable/ResponseBody/index.tsx +++ b/src/components/NetworkTable/ResponseBody/index.tsx @@ -1,7 +1,7 @@ +import { ResolvedNetworkInfo } from '@/utils'; import { dataUrlToBlob, downloadFile, semanticSize } from '../utils'; import { withPopup, usePopupRef } from '@/utils/withPopup'; import { DownloadOutlined } from '@ant-design/icons'; -import { SpyNetwork } from '@huolala-tech/page-spy-types'; import ReactJsonView from '@huolala-tech/react-json-view'; import { Form, message, Modal, Input, Alert, Button } from 'antd'; import dayjs from 'dayjs'; @@ -153,7 +153,7 @@ const EventStream = ({ data }: { data: EventData[] }) => { }; interface ResponseBodyProps { - data: SpyNetwork.RequestInfo; + data: ResolvedNetworkInfo; } export const ResponseBody = ({ data }: ResponseBodyProps) => { const bodyContent = useMemo(() => { diff --git a/src/components/NetworkTable/StatusCode/index.tsx b/src/components/NetworkTable/StatusCode/index.tsx index aa7d20b0..652f1934 100644 --- a/src/components/NetworkTable/StatusCode/index.tsx +++ b/src/components/NetworkTable/StatusCode/index.tsx @@ -1,8 +1,8 @@ -import { SpyNetwork } from '@huolala-tech/page-spy-types'; +import { ResolvedNetworkInfo } from '@/utils'; import { Space } from 'antd'; import clsx from 'clsx'; -export const StatusCode = ({ data }: { data: SpyNetwork.RequestInfo }) => { +export const StatusCode = ({ data }: { data: ResolvedNetworkInfo }) => { const { readyState, status } = data; let statusClass = ''; let statusText = status; diff --git a/src/components/NetworkTable/index.less b/src/components/NetworkTable/index.less index 9831776f..49608921 100644 --- a/src/components/NetworkTable/index.less +++ b/src/components/NetworkTable/index.less @@ -15,7 +15,7 @@ .network-table { height: 100%; - overflow: auto; + overflow-x: hidden; background-color: #fff; table { @@ -24,9 +24,8 @@ font-size: 13px; color: #4a4b4d; border-collapse: collapse; - // border: 1px solid @table-border-color; + th, td { - #mixin > .ellipsis; border-right: 1px solid @table-border-color; padding: 6px 6px; &.active { @@ -36,8 +35,26 @@ border-right: none; } } - thead tr { + thead { + position: sticky; + top: 0; + height: 30px; background-color: @thead-bg; + th { + text-align: left; + border-bottom: 1px solid @table-border-color; + } + } + tbody { + td { + #mixin > .ellipsis; + &:nth-child(1) { + cursor: pointer; + } + &:nth-child(6) { + text-align: right; + } + } } tbody { tr { @@ -57,44 +74,11 @@ } } } - - .network-list { - td { - &:nth-child(1) { - width: 20%; - } - &:nth-child(2) { - width: 38%; - } - &:nth-child(3) { - width: 12%; - } - &:nth-child(4) { - width: 10%; - } - &:nth-child(5) { - width: 10%; - } - &:nth-child(6) { - width: 10%; - } - } - &__header { - position: sticky; - top: 0; - height: 30px; - td { - border-bottom: 1px solid @table-border-color; - } - } - &__body { - td:nth-child(1) { - cursor: pointer; - } - td:nth-child(6) { - text-align: right; - } - } + .empty-table-placeholder { + position: absolute; + left: 50%; + top: 80px; + transform: translateX(-50%); } .network-detail { position: absolute; diff --git a/src/components/NetworkTable/index.tsx b/src/components/NetworkTable/index.tsx index 474bced3..d58a070e 100644 --- a/src/components/NetworkTable/index.tsx +++ b/src/components/NetworkTable/index.tsx @@ -1,23 +1,23 @@ /* eslint-disable no-case-declarations */ -import { SpyNetwork, SpyStorage } from '@huolala-tech/page-spy-types'; +import { SpyStorage } from '@huolala-tech/page-spy-types'; import { Dropdown, Empty } from 'antd'; import clsx from 'clsx'; import copy from 'copy-to-clipboard'; -import { isString } from 'lodash-es'; -import { useRef, useState, useMemo, useEffect, useCallback } from 'react'; +import { fromPairs, isArray, isString, map } from 'lodash-es'; +import { useState, useMemo, useEffect, useCallback, useRef } from 'react'; import { getStatusText, getTime } from './utils'; import { useTranslation } from 'react-i18next'; import './index.less'; import { NetworkDetail } from './NetworkDetail'; -import { NetworkMsgItem } from '@/store/socket-message'; +import { ResolvedNetworkInfo } from '@/utils'; +import { + ResizableTitle, + ResizableTitleProps, + WIDTH_CONSTRAINTS, +} from '@/components/ResizableTitle'; +import { ResizeCallbackData } from 'react-resizable'; -const networkTitle = ['Name', 'Path', 'Method', 'Status', 'Type', 'Time(≈)']; -const generalFieldMap = { - 'Request URL': 'url', - 'Request Method': 'method', -} as const; - -const getContentType = (headers: SpyNetwork.RequestInfo['requestHeader']) => { +const getContentType = (headers: ResolvedNetworkInfo['requestHeader']) => { if (!headers) return 'text/plain'; const contentType = headers.find( ([key]) => key.toLowerCase() === 'content-type', @@ -25,49 +25,39 @@ const getContentType = (headers: SpyNetwork.RequestInfo['requestHeader']) => { return contentType?.[1] || 'text/plain'; }; +type Columns = Omit< + ResizableTitleProps, + 'onResize' | 'onResizeStart' | 'onResizeStop' +>; + interface NetworkTableProps { - data: NetworkMsgItem[]; + data: ResolvedNetworkInfo[]; cookie?: SpyStorage.GetTypeDataItem['data']; + resizeCacheKey: string; } -export const NetworkTable = ({ data, cookie }: NetworkTableProps) => { +export const NetworkTable = ({ + data, + cookie, + resizeCacheKey, +}: NetworkTableProps) => { const { t: nt } = useTranslation('translation', { keyPrefix: 'network' }); - const detailClicked = useRef(false); - const [showDetail, setShowDetail] = useState(false); + const containerRef = useRef(null); + const [activeIndex, setActiveIndex] = useState(-1); const [leftDistance, setLeftDistance] = useState('20%'); - const detailData = useMemo(() => { + const detailData = useMemo(() => { if (activeIndex < 0) { return null; } return data[activeIndex]; }, [activeIndex, data]); - useEffect(() => { - const listener = (evt: MouseEvent) => { - const dom = evt.target! as HTMLElement; - if (detailClicked.current) { - detailClicked.current = false; - return; - } - if (dom.tagName === 'TD' && dom.dataset.clickout) { - setShowDetail(true); - } else { - setShowDetail(false); - } - }; - - document.addEventListener('click', listener); - return () => { - document.removeEventListener('click', listener); - }; - }, []); const hotKeyHandle = useCallback( (evt: KeyboardEvent) => { const { key } = evt; switch (key.toLocaleLowerCase()) { case 'escape': - setShowDetail(false); setActiveIndex(-1); break; case 'arrowup': @@ -90,7 +80,7 @@ export const NetworkTable = ({ data, cookie }: NetworkTableProps) => { }, [hotKeyHandle]); const onMenuClick = useCallback( - (key: string, row: SpyNetwork.RequestInfo) => { + (key: string, row: ResolvedNetworkInfo) => { switch (key) { case 'copy-link': copy(row.url); @@ -161,25 +151,103 @@ export const NetworkTable = ({ data, cookie }: NetworkTableProps) => { throw Error('Unknown key'); } }, - [], + [cookie], ); + const [columns, setColumns] = useState(() => { + const cache = localStorage.getItem(resizeCacheKey); + const value = cache && JSON.parse(cache); + return [ + { + children: 'Name', + width: value?.Name || 150, + }, + { + children: 'Path', + width: value?.Path || 300, + }, + { + children: 'Method', + width: value?.Meth || 100, + }, + { + children: 'Status', + width: value?.Stat || 100, + }, + { + children: 'Type', + width: value?.Type || 100, + }, + { + children: 'Time(≈)', + }, + ]; + }); + + const mergedColumns = useMemo(() => { + return columns.map((c, index) => ({ + ...c, + onResizeStart: () => { + const container = containerRef.current!; + + const lastColWidth = container.querySelector( + 'thead tr th:last-child', + )?.clientWidth; + if (!lastColWidth) return; + + const currentColWidth = container.querySelector( + `thead tr th:nth-child(${index + 1})`, + )?.clientWidth; + if (!currentColWidth) return; + + const [min, max] = WIDTH_CONSTRAINTS; + const diffValue = lastColWidth - min; + const newCols = [...columns]; + + let currentColMax = currentColWidth; + if (diffValue > 0) { + newCols[index].widthConstraints = [ + min, + Math.min(max, currentColMax + diffValue), + ]; + } + setColumns(newCols); + }, + onResize: (( + _: React.SyntheticEvent, + { size }: ResizeCallbackData, + ) => { + const newCols = [...columns]; + newCols[index] = { + ...newCols[index], + width: size.width, + }; + if (index === 0) { + setLeftDistance(`${size.width}px`); + } + setColumns(newCols); + }) as React.ReactEventHandler, + onResizeStop: () => { + const value = fromPairs(map(columns, (c) => [c.children, c.width])); + localStorage.setItem(resizeCacheKey, JSON.stringify(value)); + }, + })); + }, [columns, resizeCacheKey]); + return ( -
+
- +
- {networkTitle.map((t) => { - return ; + {mergedColumns.map((t) => { + return ; })} -
{t}
- {data.length > 0 ? ( - - - {data.map((row, index) => { + + {data.length > 0 ? ( + data.map((row, index) => { return ( { })} > ); - })} - -
{ setActiveIndex(index); - // setDetailData(row); setLeftDistance(evt.target.clientWidth); }} > @@ -232,27 +298,24 @@ export const NetworkTable = ({ data, cookie }: NetworkTableProps) => {
- ) : ( - - )} + }) + ) : ( + + )} + +
- {showDetail && detailData && ( + {detailData && (
{ - detailClicked.current = true; - }} > { - setShowDetail(false); + setActiveIndex(-1); }} />
diff --git a/src/components/NetworkTable/utils.ts b/src/components/NetworkTable/utils.ts index 159eb00e..c4f8b715 100644 --- a/src/components/NetworkTable/utils.ts +++ b/src/components/NetworkTable/utils.ts @@ -1,5 +1,4 @@ -import { getObjectKeys } from '@/utils'; -import { SpyNetwork } from '@huolala-tech/page-spy-types'; +import { getObjectKeys, ResolvedNetworkInfo } from '@/utils'; export function downloadFile(filename: string, url: string) { const aTag = document.createElement('a'); @@ -39,7 +38,7 @@ export function semanticSize(size: number) { return `${(size / oneMB).toFixed(1)} MB`; } -export function getStatusText(row: SpyNetwork.RequestInfo) { +export function getStatusText(row: ResolvedNetworkInfo) { if (row.readyState === 0 || row.readyState === 1) return 'Pending'; if (row.readyState === 4) { if (row.status === 0) return 'Failed'; diff --git a/src/components/ResizableTitle/cache-key.ts b/src/components/ResizableTitle/cache-key.ts new file mode 100644 index 00000000..8920e224 --- /dev/null +++ b/src/components/ResizableTitle/cache-key.ts @@ -0,0 +1,7 @@ +export const ONLINE_NETWORK_CACHE = 'online:network-table-resize-config'; +export const OFFLINE_NETWORK_CACHE = 'offline:network-table-resize-config'; + +export const ONLINE_STORAGE_CACHE = 'online:storage-table-resize-config'; +export const OFFLINE_STORAGE_CACHE = 'offline:storage-table-resize-config'; + +export const ONLINE_DB_CACHE = 'online:db-table-resize-config'; diff --git a/src/components/ResizableTitle/index.less b/src/components/ResizableTitle/index.less new file mode 100644 index 00000000..f3882c17 --- /dev/null +++ b/src/components/ResizableTitle/index.less @@ -0,0 +1,13 @@ +.resizable-title { + position: relative; + background-clip: padding-box; + .resizable-handle { + position: absolute; + right: -5px; + bottom: 0; + z-index: 1; + width: 10px; + height: 100%; + cursor: col-resize; + } +} diff --git a/src/components/ResizableTitle/index.tsx b/src/components/ResizableTitle/index.tsx new file mode 100644 index 00000000..974617b8 --- /dev/null +++ b/src/components/ResizableTitle/index.tsx @@ -0,0 +1,57 @@ +import { HTMLAttributes } from 'react'; +import { Resizable, ResizableProps } from 'react-resizable'; +import './index.less'; + +export type ResizableTitleProps = HTMLAttributes & { + onResizeStart: ResizableProps['onResizeStart']; + onResize: ResizableProps['onResize']; + onResizeStop: ResizableProps['onResizeStop']; + width?: number; + widthConstraints?: number[]; +}; + +export const WIDTH_CONSTRAINTS = [60, 500]; + +export const ResizableTitle = (props: ResizableTitleProps) => { + const { + onResize, + onResizeStart, + onResizeStop, + width, + widthConstraints = WIDTH_CONSTRAINTS, + ...rest + } = props; + + if (!width) return ; + + const minConstraints: ResizableProps['minConstraints'] = [ + widthConstraints[0], + 0, + ]; + const maxConstraints: ResizableProps['maxConstraints'] = [ + widthConstraints[1], + 0, + ]; + return ( + { + e.stopPropagation(); + }} + /> + } + onResize={onResize} + onResizeStart={onResizeStart} + onResizeStop={onResizeStop} + > + + + ); +}; diff --git a/src/components/StorageTable/index.tsx b/src/components/StorageTable/index.tsx index da234529..943d2c7f 100644 --- a/src/components/StorageTable/index.tsx +++ b/src/components/StorageTable/index.tsx @@ -3,16 +3,82 @@ import { StorageType } from '@/store/platform-config'; import { SpyStorage } from '@huolala-tech/page-spy-types'; import { Table, Tooltip } from 'antd'; import { capitalize } from 'lodash-es'; -import { useMemo } from 'react'; +import { useMemo, useRef, useState } from 'react'; +import { ResizableTitle } from '../ResizableTitle'; +import { ResizeCallbackData } from 'react-resizable'; +import { ColumnType } from 'antd/es/table/interface'; -const { Column } = Table; +const allCols = [ + { + dataIndex: 'name', + title: 'Name', + ellipsis: true, + width: 200, + }, + { + dataIndex: 'value', + title: 'Value', + ellipsis: true, + width: 300, + }, + { + dataIndex: 'domain', + title: 'Domain', + ellipsis: true, + width: 200, + }, + { + dataIndex: 'path', + title: 'Path', + ellipsis: true, + width: 100, + }, + { + dataIndex: 'expires', + title: 'Expires', + ellipsis: true, + width: 240, + render: (value: string) => { + const time = value ? new Date(value).toISOString() : 'Session'; + return ( + + {time} + + ); + }, + }, + { + dataIndex: 'secure', + title: 'Secure', + ellipsis: true, + width: 80, + render: (bool: boolean) => bool && '✅', + }, + { + dataIndex: 'sameSite', + title: 'SameSite', + ellipsis: true, + width: 80, + render: (v: string) => capitalize(v), + }, + { + dataIndex: 'partitioned', + title: 'Partitioned', + ellipsis: true, + }, +]; interface Props { activeTab: StorageType; storageMsg: Record; + resizeCacheKey: string; } -export const StorageTable = ({ activeTab, storageMsg }: Props) => { +export const StorageTable = ({ + activeTab, + storageMsg, + resizeCacheKey, +}: Props) => { const data = useMemo(() => { return Object.values(storageMsg[activeTab]); }, [activeTab, storageMsg]); @@ -21,16 +87,89 @@ export const StorageTable = ({ activeTab, storageMsg }: Props) => { return Object.keys(rest).length > 0; }, [data]); + const unionCacheKey = useMemo( + () => `${resizeCacheKey}:${activeTab}`, + [resizeCacheKey, activeTab], + ); + const cacheWidthRef = useRef>({}); + + const [columns, setColumns] = + useState[]>(allCols); + + // Init width from cache + useMemo(() => { + const cache = localStorage.getItem(unionCacheKey); + const value = cache && JSON.parse(cache); + if (value) { + cacheWidthRef.current[unionCacheKey] = value; + const widthInitedColumns = [...allCols].map((i) => { + return { + ...i, + width: value[i.title] || i.width, + }; + }); + setColumns(widthInitedColumns); + } else { + cacheWidthRef.current[unionCacheKey] = {}; + } + }, [unionCacheKey]); + + // Dragging + const mergedColumns = useMemo(() => { + let renderCols = [...columns]; + if (!hasDetail && renderCols.length !== 2) { + renderCols = renderCols.slice(0, 2); + } + const cacheWidth = cacheWidthRef.current[unionCacheKey]; + if (cacheWidth) { + renderCols.forEach((i) => { + const width = cacheWidth[i.title as string]; + if (width) { + i.width = width; + } + }); + } + + return renderCols.map((c, index) => ({ + ...c, + onHeaderCell: (column: ColumnType) => ({ + width: column.width, + onResize: (( + _: React.SyntheticEvent, + data: ResizeCallbackData, + ) => { + const { size } = data; + + const newCols = [...renderCols]; + newCols[index] = { + ...newCols[index], + width: size.width, + }; + setColumns(newCols); + + cacheWidthRef.current[unionCacheKey][column.title as string] = + size.width; + }) as React.ReactEventHandler, + onResizeStop: (_: React.SyntheticEvent, data: ResizeCallbackData) => { + const refValue = cacheWidthRef.current[unionCacheKey]; + + localStorage.setItem(unionCacheKey, JSON.stringify(refValue)); + }, + }), + })); + }, [columns, hasDetail, unionCacheKey]); + const setDetailInfo = useCacheDetailStore((state) => state.setCurrentDetail); return ( { return { onClick() { @@ -38,42 +177,11 @@ export const StorageTable = ({ activeTab, storageMsg }: Props) => { }, }; }} - > - - - {hasDetail && ( - <> - - - { - const time = value ? new Date(value).toISOString() : 'Session'; - return ( - - {time} - - ); - }} - /> - bool && '✅'} - /> - capitalize(v)} - /> - - - )} -
+ components={{ + header: { + cell: ResizableTitle, + }, + }} + /> ); }; diff --git a/src/pages/Devtools/NetworkPanel/index.tsx b/src/pages/Devtools/NetworkPanel/index.tsx index 531a3ba5..a7560306 100644 --- a/src/pages/Devtools/NetworkPanel/index.tsx +++ b/src/pages/Devtools/NetworkPanel/index.tsx @@ -7,6 +7,7 @@ import { useTranslation } from 'react-i18next'; import { useSocketMessageStore } from '@/store/socket-message'; import { NetworkTable } from '@/components/NetworkTable'; import { useForceThrottleRender } from '@/utils/useForceRender'; +import { ONLINE_NETWORK_CACHE } from '@/components/ResizableTitle/cache-key'; const NetworkPanel = memo(() => { const { t: ct } = useTranslation('translation', { keyPrefix: 'common' }); @@ -42,6 +43,7 @@ const NetworkPanel = memo(() => {
diff --git a/src/pages/Devtools/StoragePanel/StorageContent.tsx b/src/pages/Devtools/StoragePanel/StorageContent.tsx index 543e8901..7326b967 100644 --- a/src/pages/Devtools/StoragePanel/StorageContent.tsx +++ b/src/pages/Devtools/StoragePanel/StorageContent.tsx @@ -1,8 +1,8 @@ import { StorageTable } from '@/components/StorageTable'; import { StorageType } from '@/store/platform-config'; import { useSocketMessageStore } from '@/store/socket-message'; -import { SpyStorage } from '@huolala-tech/page-spy-types'; import { memo } from 'react'; +import { ONLINE_STORAGE_CACHE } from '@/components/ResizableTitle/cache-key'; interface Props { activeTab: StorageType; @@ -10,5 +10,12 @@ interface Props { export const StorageContent = memo(({ activeTab }: Props) => { const storageMsg = useSocketMessageStore((state) => state.storageMsg); - return ; + + return ( + + ); }); diff --git a/src/pages/Devtools/index.less b/src/pages/Devtools/index.less index 0efe3655..a008c992 100644 --- a/src/pages/Devtools/index.less +++ b/src/pages/Devtools/index.less @@ -49,18 +49,16 @@ &__logo { width: 46px; height: 46px; - transition: all ease-in-out 0.3s; - &:hover { - filter: drop-shadow(0 0.1em 0.3em #646cffaa); - } + filter: drop-shadow(0 2px 1px #999); + transform: translateY(-2px); } .page-spy-id { background-image: repeating-linear-gradient( -45deg, white, white 5px, - #dedede, - #dedede 10px + lighten(@primary-color, 38%), + lighten(@primary-color, 38%) 10px ); background-repeat: repeat; b { diff --git a/src/pages/Docs/components/DocContent/index.less b/src/pages/Docs/components/DocContent/index.less index f043f558..b4ffa4ba 100644 --- a/src/pages/Docs/components/DocContent/index.less +++ b/src/pages/Docs/components/DocContent/index.less @@ -44,6 +44,7 @@ border-left: 4px solid @primary-color; border-radius: 4px; background-color: #212121; + margin-bottom: @paragraph-space; p { margin: 0; } diff --git a/src/pages/Docs/md/changelog.en.mdx b/src/pages/Docs/md/changelog.en.mdx index c34e5d62..d8550de6 100644 --- a/src/pages/Docs/md/changelog.en.mdx +++ b/src/pages/Docs/md/changelog.en.mdx @@ -1,3 +1,48 @@ +import offlineLogImg from '@/assets/image/screenshot/v1.9.2-offline-log-size.png'; + +## v1.9.2 + +- The init parameters now include a `dataProcessor` option for filtering or processing data. See details: https://github.com/HuolalaTech/page-spy/pull/106; + + >
+ > Click to expand and view examples. + > + > ```ts + > window.$pageSpy = new PageSpy({ + > ..., + > dataProcessor: { + > console: (data) => { + > // log will be ignored if the content includes 'secret' + > if (data.logs.some(i => typeof i === 'string' && i.includes('secret'))) return false; + > }, + > network: (data) => { + > // ignore metric requests + > if (/(sentry|metric|collect)/.test(data.url)) return false + > },, + > storage: (data) => { + > // change the cookie to "******" if the name starts with _ + > if (data.type === "cookie" && data.action === "get") { + > data.data.forEach((i) => { + > if (i.name.startsWith("_")) { + > i.value = "*******"; + > } + > }); + > } + > }, + > }, + > }); + > ``` + >
+ +- 🆕 Adjusted the uploaded log data, see details: https://github.com/HuolalaTech/page-spy/pull/107; + + + +- 🆕 Interaction adjustments: + - Clicking on the log replay progress bar now maintains the playback state. See details: https://github.com/HuolalaTech/page-spy-web/pull/258; + - The table header supports dragging, see details: https://github.com/HuolalaTech/page-spy-web/pull/257; + - Added support for recognizing the Huawei browser, see details: https://github.com/HuolalaTech/page-spy-web/pull/256; + ## v1.9.1 - 🆕 Added the `serializeData: boolean` option to the instantiation parameters, which specifies whether the SDK is allowed to serialize non-primitive data types when collecting offline data. Default is `false`. For more details, see: https://github.com/HuolalaTech/page-spy/pull/103. diff --git a/src/pages/Docs/md/changelog.zh.mdx b/src/pages/Docs/md/changelog.zh.mdx index 380b20a8..3faefa7b 100644 --- a/src/pages/Docs/md/changelog.zh.mdx +++ b/src/pages/Docs/md/changelog.zh.mdx @@ -1,3 +1,48 @@ +import offlineLogImg from '@/assets/image/screenshot/v1.9.2-offline-log-size.png'; + +## v1.9.2 + +- 🆕 实例化参数新增 `dataProcessor` 选项用于过滤或者处理数据。查看详情:https://github.com/HuolalaTech/page-spy/pull/106 ; + + >
+ > 点击展开查看示例。 + > + > ```ts + > window.$pageSpy = new PageSpy({ + > ..., + > dataProcessor: { + > console: (data) => { + > // 打印内容中如果有 "secret" 字符则忽略(不会发送到调试端) + > if (data.logs.some(i => typeof i === 'string' && i.includes('secret'))) return false; + > }, + > network: (data) => { + > // 忽略数据打点类的请求 + > if (/(sentry|metric|collect)/.test(data.url)) return false + > },, + > storage: (data) => { + > // cookie 中的键如果是下划线开头,让调试端看到的值为 "*******" + > if (data.type === "cookie" && data.action === "get") { + > data.data.forEach((i) => { + > if (i.name.startsWith("_")) { + > i.value = "*******"; + > } + > }); + > } + > }, + > }, + > }); + > ``` + >
+ +- 🆕 调整上传的日志数据,查看详情:https://github.com/HuolalaTech/page-spy/pull/107 ; + + + +- 🆕 交互调整。 + - 点击日志回放的进度条保持播放状态,查看详情:https://github.com/HuolalaTech/page-spy-web/pull/258 ; + - 表格头支持拖拽,查看详情:https://github.com/HuolalaTech/page-spy-web/pull/257 ; + - 支持识别华为浏览器,查看详情:https://github.com/HuolalaTech/page-spy-web/pull/256 ; + ## v1.9.1 - 🆕 实例化参数新增 `serializeData: boolean` 选项,用于指定是否允许 SDK 在收集离线日志时,序列化非基本类型的数据,默认值 `false`。查看详情:https://github.com/HuolalaTech/page-spy-web/pull/241 / https://github.com/HuolalaTech/page-spy/pull/103 ; diff --git a/src/pages/LogList/index.less b/src/pages/LogList/index.less index e118128e..c0cc80ec 100644 --- a/src/pages/LogList/index.less +++ b/src/pages/LogList/index.less @@ -12,8 +12,8 @@ -45deg, white, white 5px, - #dedede, - #dedede 10px + lighten(@primary-color, 38%), + lighten(@primary-color, 38%) 10px ); background-repeat: repeat; border-radius: 4px; diff --git a/src/pages/Replay/PlayControl/components/ProgressBar.tsx b/src/pages/Replay/PlayControl/components/ProgressBar.tsx index 0a3a9197..152cf54a 100644 --- a/src/pages/Replay/PlayControl/components/ProgressBar.tsx +++ b/src/pages/Replay/PlayControl/components/ProgressBar.tsx @@ -1,7 +1,8 @@ -import { Activity, HarborDataItem, useReplayStore } from '@/store/replay'; +import { HarborDataItem, useReplayStore } from '@/store/replay'; import { Tooltip } from 'antd'; import { useRef, useEffect, useMemo, memo } from 'react'; import { useShallow } from 'zustand/react/shallow'; +import { REPLAY_PROGRESS_SKIP } from '../../events'; const ONE_MINUTE = 60 * 1000; const TEN_MINUTES = 10 * ONE_MINUTE; @@ -81,15 +82,14 @@ export const ProgressBar = memo(() => { const diff = evt.clientX - left; const progress = Math.min(diff / width, 1); - setIsPlaying(false); setProgress(progress); - flushActiveData(); + window.dispatchEvent(new CustomEvent(REPLAY_PROGRESS_SKIP)); }; el.addEventListener('click', listener); return () => { el.removeEventListener('click', listener); }; - }, [flushActiveData, setIsPlaying, setProgress]); + }, [setIsPlaying, setProgress]); // "Skip" in timeline by drag useEffect(() => { diff --git a/src/pages/Replay/PlayControl/index.tsx b/src/pages/Replay/PlayControl/index.tsx index 51ee8d2d..a44e834f 100644 --- a/src/pages/Replay/PlayControl/index.tsx +++ b/src/pages/Replay/PlayControl/index.tsx @@ -1,6 +1,5 @@ import './index.less'; import { memo, useCallback, useEffect, useRef } from 'react'; -import { REPLAY_END, REPLAY_PROGRESS_CHANGE } from '../events'; import { fixProgress, useReplayStore } from '@/store/replay'; import { useEventListener } from '@/utils/useEventListener'; import { CurrentTime } from './components/CurrentTime'; @@ -9,6 +8,7 @@ import { Actions } from './components/Actions'; import { Duration } from './components/Duration'; import { useShallow } from 'zustand/react/shallow'; import { throttle } from 'lodash-es'; +import { REPLAY_PROGRESS_SKIP } from '../events'; export const PlayControl = memo(() => { const [duration, speed, isPlaying, setIsPlaying, setProgress] = @@ -21,11 +21,6 @@ export const PlayControl = memo(() => { state.setProgress, ]), ); - const flushActiveData = useRef( - throttle(useReplayStore.getState().flushActiveData, 100), - ); - const elapsed = useRef(0); - const raf = useRef(0); useEventListener( 'keyup', @@ -37,6 +32,17 @@ export const PlayControl = memo(() => { }, { target: document }, ); + const flushActiveData = useRef( + throttle(useReplayStore.getState().flushActiveData, 100), + ); + const elapsed = useRef(0); + const raf = useRef(0); + + useEventListener(REPLAY_PROGRESS_SKIP, () => { + const { progress, duration } = useReplayStore.getState(); + elapsed.current = progress * duration; + flushActiveData.current(); + }); const rafHandler = useCallback(() => { if (!duration) return; @@ -44,16 +50,8 @@ export const PlayControl = memo(() => { const progress = fixProgress(elapsed.current / duration); setProgress(progress); - if (progress === 1) { setIsPlaying(false); - window.dispatchEvent(new CustomEvent(REPLAY_END)); - } else { - window.dispatchEvent( - new CustomEvent(REPLAY_PROGRESS_CHANGE, { - detail: elapsed.current, - }), - ); } flushActiveData.current(); diff --git a/src/pages/Replay/PluginPanel/components/NetworkPanel/index.tsx b/src/pages/Replay/PluginPanel/components/NetworkPanel/index.tsx index 2949a075..a151e334 100644 --- a/src/pages/Replay/PluginPanel/components/NetworkPanel/index.tsx +++ b/src/pages/Replay/PluginPanel/components/NetworkPanel/index.tsx @@ -3,9 +3,12 @@ import { useReplayStore } from '@/store/replay'; import './index.less'; import { memo } from 'react'; import { useShallow } from 'zustand/react/shallow'; +import { OFFLINE_NETWORK_CACHE } from '@/components/ResizableTitle/cache-key'; export const NetworkPanel = memo(() => { const networkMsg = useReplayStore(useShallow((state) => state.networkMsg)); - return ; + return ( + + ); }); diff --git a/src/pages/Replay/PluginPanel/components/StoragePanel/StorageContent.tsx b/src/pages/Replay/PluginPanel/components/StoragePanel/StorageContent.tsx index ac3e5d3d..523006a2 100644 --- a/src/pages/Replay/PluginPanel/components/StoragePanel/StorageContent.tsx +++ b/src/pages/Replay/PluginPanel/components/StoragePanel/StorageContent.tsx @@ -1,3 +1,4 @@ +import { OFFLINE_STORAGE_CACHE } from '@/components/ResizableTitle/cache-key'; import { StorageTable } from '@/components/StorageTable'; import { useReplayStore } from '@/store/replay'; import { SpyStorage } from '@huolala-tech/page-spy-types'; @@ -11,5 +12,11 @@ interface Props { export const StorageContent = memo(({ activeTab }: Props) => { const storageMsg = useReplayStore(useShallow((state) => state.storageMsg)); - return ; + return ( + + ); }); diff --git a/src/pages/Replay/RRWebPlayer/index.tsx b/src/pages/Replay/RRWebPlayer/index.tsx index 8b435c13..416a86f9 100644 --- a/src/pages/Replay/RRWebPlayer/index.tsx +++ b/src/pages/Replay/RRWebPlayer/index.tsx @@ -1,6 +1,10 @@ import { useEventListener } from '@/utils/useEventListener'; -import { memo, useEffect, useMemo, useRef } from 'react'; -import { PLAYER_SIZE_CHANGE, REPLAY_STATUS_CHANGE } from '../events'; +import { memo, useCallback, useEffect, useMemo, useRef } from 'react'; +import { + PLAYER_SIZE_CHANGE, + REPLAY_PROGRESS_SKIP, + REPLAY_STATUS_CHANGE, +} from '../events'; import { useReplayStore } from '@/store/replay'; import rrwebPlayer from 'rrweb-player'; import './index.less'; @@ -32,13 +36,16 @@ export const RRWebPlayer = memo(() => { return JSON.parse(JSON.stringify(allRRwebEvent)); }, [allRRwebEvent]); - useEventListener(REPLAY_STATUS_CHANGE, (evt) => { - const { status, elapsed } = (evt as CustomEvent).detail; + const onGoto = useCallback(() => { const player = playerInstance.current; if (!player) return; - player.goto(elapsed, status === 'playing'); - }); + const { isPlaying, progress, duration } = useReplayStore.getState(); + player.goto(progress * duration, isPlaying); + }, []); + + useEventListener(REPLAY_STATUS_CHANGE, onGoto); + useEventListener(REPLAY_PROGRESS_SKIP, onGoto); useEventListener(PLAYER_SIZE_CHANGE, () => { const { width, height } = rootEl.current!.getBoundingClientRect(); diff --git a/src/pages/Replay/events.ts b/src/pages/Replay/events.ts index 250d77cb..f39811c4 100644 --- a/src/pages/Replay/events.ts +++ b/src/pages/Replay/events.ts @@ -1,5 +1,3 @@ export const REPLAY_STATUS_CHANGE = 'replay-status-change'; -export const REPLAY_PROGRESS_CHANGE = 'replay-progress-change'; -export const REPLAY_END = 'replay-end'; -export const RRWEB_PLAYER_FULLSCREEN = 'rrweb-player-fullscreen'; +export const REPLAY_PROGRESS_SKIP = 'replay-progress-skip'; export const PLAYER_SIZE_CHANGE = 'player-size-change'; diff --git a/src/store/replay.ts b/src/store/replay.ts index 4d78de2b..8dbc89ed 100644 --- a/src/store/replay.ts +++ b/src/store/replay.ts @@ -2,7 +2,6 @@ import { SpyConsole, SpyMessage, - SpyNetwork, SpyStorage, SpySystem, } from '@huolala-tech/page-spy-types'; @@ -11,7 +10,11 @@ import { eventWithTime } from '@rrweb/types'; import { produce } from 'immer'; import { isEqual, omit } from 'lodash-es'; import { REPLAY_STATUS_CHANGE } from '@/pages/Replay/events'; -import { isRRWebClickEvent, resolveUrlInfo } from '@/utils'; +import { + isRRWebClickEvent, + ResolvedNetworkInfo, + resolveUrlInfo, +} from '@/utils'; const isCaredActivity = (activity: HarborDataItem) => { const { type, data } = activity; @@ -55,7 +58,7 @@ export interface ReplayStore { setRRWebStartTime: (timestamp: number) => void; activity: Activity[]; allConsoleMsg: HarborDataItem[]; - allNetworkMsg: HarborDataItem[]; + allNetworkMsg: HarborDataItem[]; allRRwebEvent: eventWithTime[]; allStorageMsg: HarborDataItem[]; allSystemMsg: SpySystem.DataItem[]; @@ -63,7 +66,7 @@ export interface ReplayStore { endTime: number; duration: number; consoleMsg: SpyConsole.DataItem[]; - networkMsg: SpyNetwork.RequestInfo[]; + networkMsg: ResolvedNetworkInfo[]; storageMsg: Record; setAllData: (data: HarborDataItem[]) => void; flushActiveData: () => void; @@ -279,7 +282,7 @@ export const useReplayStore = create((set, get) => ({ const { allNetworkMsg, networkMsg } = get(); let networkIndex = 0; - const showedNetworkMsg: Map = new Map(); + const showedNetworkMsg: Map = new Map(); while ( networkIndex < allNetworkMsg.length && allNetworkMsg[networkIndex].timestamp <= currentTime @@ -396,15 +399,7 @@ export const useReplayStore = create((set, get) => ({ state.isPlaying = value; }), ); - const { progress, duration } = get(); - window.dispatchEvent( - new CustomEvent(REPLAY_STATUS_CHANGE, { - detail: { - status: value ? 'playing' : 'paused', - elapsed: progress * duration, - }, - }), - ); + window.dispatchEvent(new CustomEvent(REPLAY_STATUS_CHANGE)); }, timeMode: TIME_MODE.RELATED, setTimeMode(mode) { diff --git a/src/store/socket-message/index.ts b/src/store/socket-message/index.ts index 48968119..42637e59 100644 --- a/src/store/socket-message/index.ts +++ b/src/store/socket-message/index.ts @@ -12,7 +12,7 @@ import { SpyClient, } from '@huolala-tech/page-spy-types'; import { API_BASE_URL } from '@/apis/request'; -import { ResolvedUrlInfo, resolveProtocol, resolveUrlInfo } from '@/utils'; +import { ResolvedNetworkInfo, resolveProtocol, resolveUrlInfo } from '@/utils'; import { ElementContent } from 'hast'; import { getFixedPageMsg, processMPNetworkMsg } from './utils'; import { isArray, isEqual, isString, omit } from 'lodash-es'; @@ -21,15 +21,13 @@ import { StorageType } from '../platform-config'; const USER_ID = 'Debugger'; -export type NetworkMsgItem = SpyNetwork.RequestInfo & ResolvedUrlInfo; - interface SocketMessage { socket: SocketStore | null; clientInfo: ParsedClientInfo | null; consoleMsg: SpyConsole.DataItem[]; consoleMsgTypeFilter: string[]; consoleMsgKeywordFilter: string; - networkMsg: NetworkMsgItem[]; + networkMsg: ResolvedNetworkInfo[]; systemMsg: SpySystem.DataItem[]; connectMsg: string[]; pageMsg: { @@ -112,7 +110,7 @@ export const useSocketMessageStore = create((set, get) => ({ socket.addListener('network', (data: SpyNetwork.RequestInfo) => { const { name, pathname, getData } = resolveUrlInfo(data.url); - const newData: NetworkMsgItem = { + const newData: ResolvedNetworkInfo = { ...data, name, pathname, diff --git a/src/store/socket-message/utils.ts b/src/store/socket-message/utils.ts index 69e8dea9..7a4a8877 100644 --- a/src/store/socket-message/utils.ts +++ b/src/store/socket-message/utils.ts @@ -1,5 +1,4 @@ -import { resolveUrlInfo } from '@/utils'; -import { SpyNetwork } from '@huolala-tech/page-spy-types'; +import { ResolvedNetworkInfo } from '@/utils'; import { ElementContent, Root } from 'hast'; import rehypeParse from 'rehype-parse'; import rehypeStringify from 'rehype-stringify'; @@ -51,7 +50,7 @@ export const getFixedPageMsg = async (htmlText: string, base: string) => { }; // 处理小程序网络信息 -export const processMPNetworkMsg = (data: SpyNetwork.RequestInfo) => { +export const processMPNetworkMsg = (data: ResolvedNetworkInfo) => { // why the requestPayload should be string? because the js object will be stringified in sdk. if ( data.url && diff --git a/src/utils/brand.ts b/src/utils/brand.ts index 899632c3..305881e4 100644 --- a/src/utils/brand.ts +++ b/src/utils/brand.ts @@ -26,6 +26,7 @@ import mpDingtalkSvg from '@/assets/image/mp-dingtalk.svg'; import mpAlipaySvg from '@/assets/image/mp-alipay.svg'; import mpXhsSvg from '@/assets/image/mp-xhs.svg'; import reactSvg from '@/assets/image/react.svg'; +import huaweiSvg from '@/assets/image/huawei-browser.svg'; import uniSvg from '@/assets/image/uni.svg'; import { SpyClient } from '@huolala-tech/page-spy-types'; @@ -144,7 +145,7 @@ export const BROWSER_CONFIG: Record< 'mp-xhs': { logo: mpXhsSvg, label: t('common.mpxhs') }, 'mp-uni': { logo: uniSvg, label: 'Uni APP' }, 'uni-native': { logo: uniSvg, label: 'Uni APP' }, - harmony: { logo: pcSvg, label: 'Harmony' }, + harmony: { logo: huaweiSvg, label: 'Huawei' }, 'react-native': { logo: reactSvg, label: 'React Native' }, }; @@ -177,13 +178,13 @@ const BROWSER_REGEXPS = { wechat: /MicroMessenger\/([\d.]+)/, qq: /(?:QQBrowser|MQQBrowser|QQ)\/([\d.]+)/, uc: /(?:UCBrowser|UCBS)\/([\d.]+)/, + harmony: /(?:HuaweiBrowser)\/([\d.]+)/, baidu: /(?:BIDUBrowser|baiduboxapp)[/]?([\d.]*)/, edge: /Edg(?:e|A|iOS)?\/([\d.]+)/, chrome: /(?:Chrome|CriOS)\/([\d.]+)/, firefox: /(?:Firefox|FxiOS)\/([\d.]+)/, safari: /Version\/([\d.]+).*Safari/, 'uni-native': /uni-native\/([\d.]+)/, - harmony: /\sharmony\/(.*)$/, 'react-native': /react-native\/([\d.]+)/, ...MP_REGEXPS, } as Record; @@ -195,7 +196,7 @@ const OS_REGEXPS = { mac: /(Mac OS X |macos\/)([\d_.]+)/, android: /(Android |android\/)([\d_.]+)/, linux: /Linux/, - harmony: /(harmony\/)([\d_.]+[\(\w\)]*)/, + harmony: /(OpenHarmony )([\d_.]+)/, } as Record; export function parseUserAgent(uaString: string = window.navigator.userAgent) { @@ -248,35 +249,6 @@ export function parseUserAgent(uaString: string = window.navigator.userAgent) { }; } -// export const parseDeviceInfo = (device: string): DeviceInfo => { -// const reg = /(.*)\/(.*)\s(.*)\/(.*)/; -// const result = device.match(reg); -// if (result === null) -// return { -// osName: 'unknown', -// osVersion: 'unknown', -// browserName: 'unknown', -// browserVersion: 'unknown', -// }; - -// const [_, osName, osVersion, browserName, browserVersion] = result; -// return { -// osName: osName.toLowerCase(), -// osVersion, -// browserName: browserName.toLowerCase(), -// browserVersion, -// osLogo: getOSLogo(osName.toLowerCase()), -// browserLogo: getBrowserLogo(browserName.toLowerCase()), -// } as DeviceInfo; -// }; - -// get client info from room info -// export function getClientInfoFromRoom(room: I.SpyRoom) { -// const {name, tags} = room -// const ua = tags.ua || name -// return parseUserAgent(ua) -// } - // get client info from system message export function parseClientInfo(msg: SpyClient.DataItem): ParsedClientInfo { const { ua = '', sdk = 'unknown', plugins = [], isDevTools } = msg; diff --git a/src/utils/index.ts b/src/utils/index.ts index 1c3d38fe..f014dbda 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,3 +1,4 @@ +import { SpyNetwork } from '@huolala-tech/page-spy-types'; import { EventType, eventWithTime, @@ -89,15 +90,17 @@ export function resolveUrlInfo(url: string): ResolvedUrlInfo { pathname, getData: [...searchParams.entries()], }; - } /* c8 ignore start */ catch (e) { + } catch (e) { return { name: url, pathname: '', getData: null, }; - } /* c8 ignore stop */ + } } +export type ResolvedNetworkInfo = SpyNetwork.RequestInfo & ResolvedUrlInfo; + interface RRWebClickEvent { type: EventType.IncrementalSnapshot; data: mouseInteractionData & { diff --git a/yarn.lock b/yarn.lock index 5dbb674d..38830c5c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -735,46 +735,48 @@ "@huolala-tech/page-spy-api-win32-arm" "1.9.0" "@huolala-tech/page-spy-api-win32-arm64" "1.9.0" -"@huolala-tech/page-spy-base@^1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@huolala-tech/page-spy-base/-/page-spy-base-1.0.2.tgz#4467d1e910092d323c5a2c11ca05cde76ab4e562" - integrity sha512-XioKWw3qPx76ohiV65/t8OYr/TauPfErUq3ElNFDocotiTt+LKK3n4Rj7WkaoamyUD+HVEM+AF/AkG7R77AcDA== +"@huolala-tech/page-spy-base@^1.0.6": + version "1.0.6" + resolved "https://registry.yarnpkg.com/@huolala-tech/page-spy-base/-/page-spy-base-1.0.6.tgz#c6f0bcd893e7e07f371e2fa57be6e9a2df931596" + integrity sha512-7r87TjCtxqbqtmXBMalPo7Dl03SVRUpczj+2gSFt8BF0hI4a6OWnTfmRKM0w+w92mBnJwpPbYCkfC5v6+EPlEA== dependencies: - "@huolala-tech/page-spy-types" "^1.9.2" + "@huolala-tech/page-spy-types" "^1.9.6" -"@huolala-tech/page-spy-browser@^1.9.3": - version "1.9.3" - resolved "https://registry.yarnpkg.com/@huolala-tech/page-spy-browser/-/page-spy-browser-1.9.3.tgz#f5eee26539ebc8a0d5a01a243e89eeab16acccdb" - integrity sha512-4THkcGlPj6TJzjEehvGuuKtr59W7TvNXLdGAz0ZGZQL5mY04irdJ6ap6yMbPiKAyedxRp6061bHaGtudlOgfBw== +"@huolala-tech/page-spy-browser@^1.9.8": + version "1.9.8" + resolved "https://registry.yarnpkg.com/@huolala-tech/page-spy-browser/-/page-spy-browser-1.9.8.tgz#56772a20bf31d1d82e44b242133509e83de60ae1" + integrity sha512-l1BHrwBNMIK4m6V++olgAp+Le0lCMMJuMB1PwGQgIWzyErlxf4b23/DFeAtW0bv4IfETTwUb+s4NktuVDVryrw== dependencies: "@babel/runtime" "^7.13.0" - "@huolala-tech/page-spy-base" "^1.0.2" - "@huolala-tech/page-spy-types" "^1.9.2" + "@huolala-tech/page-spy-base" "^1.0.6" + "@huolala-tech/page-spy-types" "^1.9.6" copy-to-clipboard "^3.3.1" -"@huolala-tech/page-spy-plugin-data-harbor@^1.3.3": - version "1.3.3" - resolved "https://registry.yarnpkg.com/@huolala-tech/page-spy-plugin-data-harbor/-/page-spy-plugin-data-harbor-1.3.3.tgz#fd3a8074b55ec36984c896a0d2c9d0df95f39505" - integrity sha512-9Tcw250vX+sabueWyyjHGFeCHQTDqDRmGz76s8bGzR59EoBjQBw46J9bXtc+qWao3+UvHFS17gKZcbL/WoNW2Q== +"@huolala-tech/page-spy-plugin-data-harbor@^1.3.7": + version "1.3.7" + resolved "https://registry.yarnpkg.com/@huolala-tech/page-spy-plugin-data-harbor/-/page-spy-plugin-data-harbor-1.3.7.tgz#d76ea6ee3c4c9b7dd3f6a56304eb948aeda9ba5b" + integrity sha512-SQpL0qhFfcX9e3YVkF/2wMRaVBA6+a9u9xi1qCM3UPtqtAvqqtnmr3QUVwGHHXfTwQg+00qIhRmE5HDuBlZEfw== dependencies: "@babel/runtime" "^7.13.0" - "@huolala-tech/page-spy-base" "^1.0.2" + "@huolala-tech/page-spy-base" "^1.0.6" fflate "^0.8.1" -"@huolala-tech/page-spy-plugin-rrweb@^1.2.3": - version "1.2.3" - resolved "https://registry.yarnpkg.com/@huolala-tech/page-spy-plugin-rrweb/-/page-spy-plugin-rrweb-1.2.3.tgz#41a4421f5d2559dbd0bf66821e75a7b3ae9cad9d" - integrity sha512-847Mj7n74gMQbv1bPXC1FHLeGxk56+NxQsVdbjicns8zGEUsnqphCS/K9u0RoMsgYHNCWAHv4Gwi3eLtokcB5Q== +"@huolala-tech/page-spy-plugin-rrweb@^1.2.7": + version "1.2.7" + resolved "https://registry.yarnpkg.com/@huolala-tech/page-spy-plugin-rrweb/-/page-spy-plugin-rrweb-1.2.7.tgz#ee46416a1ca72c3c94b2a35c7989ccf5ddaad8d7" + integrity sha512-TPuQfKep0hR33aaSnM8NoKTClYDjKcrovjn51G3r5P+E4JOSHyMJZx4q+Z1t4kkKcRr260vJNlcpi7C8CpbISg== dependencies: "@babel/runtime" "^7.13.0" - "@huolala-tech/page-spy-base" "^1.0.2" - "@huolala-tech/page-spy-types" "^1.9.2" + "@huolala-tech/page-spy-base" "^1.0.6" + "@huolala-tech/page-spy-types" "^1.9.6" rrweb "^2.0.0-alpha.4" -"@huolala-tech/page-spy-types@^1.9.2": - version "1.9.2" - resolved "https://registry.yarnpkg.com/@huolala-tech/page-spy-types/-/page-spy-types-1.9.2.tgz#d0fdbd10208b3b0ec6439fead2e2243466877904" - integrity sha512-6PL/NwT7nH5JL1cAhuKvySRM68iNrQXGJuzAVufttXGEdGuM9vCSpizAbjwHQ3W83vituLFuryU6RSaZvJSj9w== +"@huolala-tech/page-spy-types@^1.9.6": + version "1.9.6" + resolved "https://registry.yarnpkg.com/@huolala-tech/page-spy-types/-/page-spy-types-1.9.6.tgz#5e4df9618c6ebe5c567f53e3ad60715f1fb77141" + integrity sha512-owWTDxcm+qm2EDbBTBdPNixBSDRwAY/eAb05c7aZbByN3F8epXlqMTWfiHuFeM7f3Z6604fDv0nXad9hhala1A== + dependencies: + "@huolala-tech/page-spy-base" "^1.0.6" "@huolala-tech/react-json-view@^1.2.5": version "1.2.5"