mirror of
https://github.com/traefik/traefik
synced 2026-02-03 11:10:33 +00:00
Merge v3.6 into master
This commit is contained in:
@@ -1 +1,2 @@
|
||||
nodeLinker: node-modules
|
||||
enableScripts: false
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,131 @@
|
||||
import { renderHook, waitFor } from '@testing-library/react'
|
||||
import { ReactNode } from 'react'
|
||||
|
||||
import useHubUpgradeButton from './use-hub-upgrade-button'
|
||||
|
||||
import { VersionContext } from 'contexts/version'
|
||||
import verifySignature from 'utils/workers/scriptVerification'
|
||||
|
||||
vi.mock('utils/workers/scriptVerification')
|
||||
|
||||
const mockVerifySignature = vi.mocked(verifySignature)
|
||||
|
||||
const createWrapper = (showHubButton: boolean) => {
|
||||
return ({ children }: { children: ReactNode }) => (
|
||||
<VersionContext.Provider value={{ showHubButton, version: '1.0.0' }}>{children}</VersionContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
describe('useHubUpgradeButton Hook', () => {
|
||||
let originalCreateObjectURL: typeof URL.createObjectURL
|
||||
let originalRevokeObjectURL: typeof URL.revokeObjectURL
|
||||
const mockBlobUrl = 'blob:http://localhost:3000/mock-blob-url'
|
||||
|
||||
beforeEach(() => {
|
||||
originalCreateObjectURL = URL.createObjectURL
|
||||
originalRevokeObjectURL = URL.revokeObjectURL
|
||||
URL.createObjectURL = vi.fn(() => mockBlobUrl)
|
||||
URL.revokeObjectURL = vi.fn()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
URL.createObjectURL = originalCreateObjectURL
|
||||
URL.revokeObjectURL = originalRevokeObjectURL
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
it('should not verify script when showHubButton is false', async () => {
|
||||
renderHook(() => useHubUpgradeButton(), {
|
||||
wrapper: createWrapper(false),
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockVerifySignature).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
it('should verify script and create blob URL when showHubButton is true and verification succeeds', async () => {
|
||||
const mockScriptContent = new ArrayBuffer(8)
|
||||
mockVerifySignature.mockResolvedValue({
|
||||
verified: true,
|
||||
scriptContent: mockScriptContent,
|
||||
})
|
||||
|
||||
const { result } = renderHook(() => useHubUpgradeButton(), {
|
||||
wrapper: createWrapper(true),
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockVerifySignature).toHaveBeenCalledWith(
|
||||
'https://traefik.github.io/traefiklabs-hub-button-app/main-v1.js',
|
||||
'https://traefik.github.io/traefiklabs-hub-button-app/main-v1.js.sig',
|
||||
'MCowBQYDK2VwAyEAY0OZFFE5kSuqYK6/UprTL5RmvQ+8dpPTGMCw1MiO/Gs=',
|
||||
)
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.signatureVerified).toBe(true)
|
||||
})
|
||||
|
||||
expect(result.current.scriptBlobUrl).toBe(mockBlobUrl)
|
||||
expect(URL.createObjectURL).toHaveBeenCalledWith(expect.any(Blob))
|
||||
})
|
||||
|
||||
it('should set signatureVerified to false when verification fails', async () => {
|
||||
mockVerifySignature.mockResolvedValue({
|
||||
verified: false,
|
||||
})
|
||||
|
||||
const { result } = renderHook(() => useHubUpgradeButton(), {
|
||||
wrapper: createWrapper(true),
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockVerifySignature).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.signatureVerified).toBe(false)
|
||||
})
|
||||
|
||||
expect(result.current.scriptBlobUrl).toBeNull()
|
||||
expect(URL.createObjectURL).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should handle verification errors gracefully', async () => {
|
||||
mockVerifySignature.mockRejectedValue(new Error('Verification failed'))
|
||||
|
||||
const { result } = renderHook(() => useHubUpgradeButton(), {
|
||||
wrapper: createWrapper(true),
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockVerifySignature).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.signatureVerified).toBe(false)
|
||||
})
|
||||
|
||||
expect(result.current.scriptBlobUrl).toBeNull()
|
||||
})
|
||||
|
||||
it('should create blob with correct MIME type', async () => {
|
||||
const mockScriptContent = new ArrayBuffer(8)
|
||||
mockVerifySignature.mockResolvedValue({
|
||||
verified: true,
|
||||
scriptContent: mockScriptContent,
|
||||
})
|
||||
|
||||
renderHook(() => useHubUpgradeButton(), {
|
||||
wrapper: createWrapper(true),
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(URL.createObjectURL).toHaveBeenCalled()
|
||||
})
|
||||
const blobCall = vi.mocked(URL.createObjectURL).mock.calls[0][0] as Blob
|
||||
expect(blobCall).toBeInstanceOf(Blob)
|
||||
expect(blobCall.type).toBe('application/javascript')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,61 @@
|
||||
import { useContext, useEffect, useState } from 'react'
|
||||
|
||||
import { VersionContext } from 'contexts/version'
|
||||
import verifySignature from 'utils/workers/scriptVerification'
|
||||
|
||||
const HUB_BUTTON_URL = 'https://traefik.github.io/traefiklabs-hub-button-app/main-v1.js'
|
||||
const PUBLIC_KEY = 'MCowBQYDK2VwAyEAY0OZFFE5kSuqYK6/UprTL5RmvQ+8dpPTGMCw1MiO/Gs='
|
||||
|
||||
const useHubUpgradeButton = () => {
|
||||
const [signatureVerified, setSignatureVerified] = useState(false)
|
||||
const [scriptBlobUrl, setScriptBlobUrl] = useState<string | null>(null)
|
||||
const [isCustomElementDefined, setIsCustomElementDefined] = useState(false)
|
||||
|
||||
const { showHubButton } = useContext(VersionContext)
|
||||
|
||||
useEffect(() => {
|
||||
if (showHubButton) {
|
||||
if (customElements.get('hub-button-app')) {
|
||||
setSignatureVerified(true)
|
||||
setIsCustomElementDefined(true)
|
||||
return
|
||||
}
|
||||
|
||||
const verifyAndLoadScript = async () => {
|
||||
try {
|
||||
const { verified, scriptContent: content } = await verifySignature(
|
||||
HUB_BUTTON_URL,
|
||||
`${HUB_BUTTON_URL}.sig`,
|
||||
PUBLIC_KEY,
|
||||
)
|
||||
if (!verified || !content) {
|
||||
setSignatureVerified(false)
|
||||
} else {
|
||||
const blob = new Blob([content], { type: 'application/javascript' })
|
||||
const blobUrl = URL.createObjectURL(blob)
|
||||
|
||||
setScriptBlobUrl(blobUrl)
|
||||
setSignatureVerified(true)
|
||||
}
|
||||
} catch {
|
||||
setSignatureVerified(false)
|
||||
}
|
||||
}
|
||||
|
||||
verifyAndLoadScript()
|
||||
|
||||
return () => {
|
||||
setScriptBlobUrl((currentUrl) => {
|
||||
if (currentUrl) {
|
||||
URL.revokeObjectURL(currentUrl)
|
||||
}
|
||||
return null
|
||||
})
|
||||
}
|
||||
}
|
||||
}, [showHubButton])
|
||||
|
||||
return { signatureVerified, scriptBlobUrl, isCustomElementDefined }
|
||||
}
|
||||
|
||||
export default useHubUpgradeButton
|
||||
@@ -1,9 +1,20 @@
|
||||
import { Flex, Text } from '@traefiklabs/faency'
|
||||
import { AriaTd, Flex, Text } from '@traefiklabs/faency'
|
||||
import { FiAlertTriangle } from 'react-icons/fi'
|
||||
|
||||
export const EmptyPlaceholder = ({ message = 'No data available' }: { message?: string }) => (
|
||||
type EmptyPlaceholderProps = {
|
||||
message?: string
|
||||
}
|
||||
export const EmptyPlaceholder = ({ message = 'No data available' }: EmptyPlaceholderProps) => (
|
||||
<Flex align="center" justify="center" css={{ py: '$5', color: '$primary' }}>
|
||||
<FiAlertTriangle size={16} />
|
||||
<Text css={{ pl: '$2' }}>{message}</Text>
|
||||
</Flex>
|
||||
)
|
||||
|
||||
export const EmptyPlaceholderTd = (props: EmptyPlaceholderProps) => {
|
||||
return (
|
||||
<AriaTd css={{ pointerEvents: 'none' }} fullColSpan>
|
||||
<EmptyPlaceholder {...props} />
|
||||
</AriaTd>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import SortableTh from 'components/tables/SortableTh'
|
||||
import Tooltip from 'components/Tooltip'
|
||||
import TooltipText from 'components/TooltipText'
|
||||
import useFetchWithPagination, { pagesResponseInterface, RenderRowType } from 'hooks/use-fetch-with-pagination'
|
||||
import { EmptyPlaceholder } from 'layout/EmptyPlaceholder'
|
||||
import { EmptyPlaceholderTd } from 'layout/EmptyPlaceholder'
|
||||
import { parseMiddlewareType } from 'libs/parsers'
|
||||
|
||||
export const makeRowRender = (): RenderRowType => {
|
||||
@@ -79,9 +79,7 @@ export const HttpMiddlewaresRender = ({
|
||||
{(isEmpty || !!error) && (
|
||||
<AriaTfoot>
|
||||
<AriaTr>
|
||||
<AriaTd fullColSpan>
|
||||
<EmptyPlaceholder message={error ? 'Failed to fetch data' : 'No data available'} />
|
||||
</AriaTd>
|
||||
<EmptyPlaceholderTd message={error ? 'Failed to fetch data' : 'No data available'} />
|
||||
</AriaTr>
|
||||
</AriaTfoot>
|
||||
)}
|
||||
|
||||
@@ -16,7 +16,7 @@ import SortableTh from 'components/tables/SortableTh'
|
||||
import Tooltip from 'components/Tooltip'
|
||||
import TooltipText from 'components/TooltipText'
|
||||
import useFetchWithPagination, { pagesResponseInterface, RenderRowType } from 'hooks/use-fetch-with-pagination'
|
||||
import { EmptyPlaceholder } from 'layout/EmptyPlaceholder'
|
||||
import { EmptyPlaceholderTd } from 'layout/EmptyPlaceholder'
|
||||
|
||||
export const makeRowRender = (protocol = 'http'): RenderRowType => {
|
||||
const HttpRoutersRenderRow = (row) => (
|
||||
@@ -100,9 +100,7 @@ export const HttpRoutersRender = ({
|
||||
{(isEmpty || !!error) && (
|
||||
<AriaTfoot>
|
||||
<AriaTr>
|
||||
<AriaTd fullColSpan>
|
||||
<EmptyPlaceholder message={error ? 'Failed to fetch data' : 'No data available'} />
|
||||
</AriaTd>
|
||||
<EmptyPlaceholderTd message={error ? 'Failed to fetch data' : 'No data available'} />
|
||||
</AriaTr>
|
||||
</AriaTfoot>
|
||||
)}
|
||||
|
||||
@@ -14,7 +14,7 @@ import SortableTh from 'components/tables/SortableTh'
|
||||
import Tooltip from 'components/Tooltip'
|
||||
import TooltipText from 'components/TooltipText'
|
||||
import useFetchWithPagination, { pagesResponseInterface, RenderRowType } from 'hooks/use-fetch-with-pagination'
|
||||
import { EmptyPlaceholder } from 'layout/EmptyPlaceholder'
|
||||
import { EmptyPlaceholderTd } from 'layout/EmptyPlaceholder'
|
||||
|
||||
export const makeRowRender = (): RenderRowType => {
|
||||
const HttpServicesRenderRow = (row) => (
|
||||
@@ -78,9 +78,7 @@ export const HttpServicesRender = ({
|
||||
{(isEmpty || !!error) && (
|
||||
<AriaTfoot>
|
||||
<AriaTr>
|
||||
<AriaTd fullColSpan>
|
||||
<EmptyPlaceholder message={error ? 'Failed to fetch data' : 'No data available'} />
|
||||
</AriaTd>
|
||||
<EmptyPlaceholderTd message={error ? 'Failed to fetch data' : 'No data available'} />
|
||||
</AriaTr>
|
||||
</AriaTfoot>
|
||||
)}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { waitFor } from '@testing-library/react'
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
||||
|
||||
import { PUBLIC_KEY } from './constants'
|
||||
import HubDashboard, { resetCache } from './HubDashboard'
|
||||
import verifySignature from './workers/scriptVerification'
|
||||
|
||||
import { renderWithProviders } from 'utils/test'
|
||||
import verifySignature from 'utils/workers/scriptVerification'
|
||||
|
||||
vi.mock('./workers/scriptVerification', () => ({
|
||||
vi.mock('utils/workers/scriptVerification', () => ({
|
||||
default: vi.fn(),
|
||||
}))
|
||||
|
||||
@@ -34,7 +35,6 @@ describe('HubDashboard demo', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
|
||||
// Mock URL.createObjectURL
|
||||
mockCreateObjectURL = vi.fn(() => 'blob:mock-url')
|
||||
globalThis.URL.createObjectURL = mockCreateObjectURL
|
||||
})
|
||||
@@ -45,7 +45,6 @@ describe('HubDashboard demo', () => {
|
||||
|
||||
describe('without cache', () => {
|
||||
beforeEach(() => {
|
||||
// Reset cache before each test suites
|
||||
resetCache()
|
||||
})
|
||||
|
||||
@@ -130,6 +129,7 @@ describe('HubDashboard demo', () => {
|
||||
expect(mockVerifyScriptSignature).toHaveBeenCalledWith(
|
||||
'https://assets.traefik.io/hub-ui-demo.js',
|
||||
'https://assets.traefik.io/hub-ui-demo.js.sig',
|
||||
PUBLIC_KEY,
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -3,7 +3,9 @@ import { useMemo, useEffect, useState } from 'react'
|
||||
import { Helmet } from 'react-helmet-async'
|
||||
import { useParams } from 'react-router-dom'
|
||||
|
||||
import verifySignature from './workers/scriptVerification'
|
||||
import verifySignature from '../../utils/workers/scriptVerification'
|
||||
|
||||
import { PUBLIC_KEY } from './constants'
|
||||
|
||||
import { SpinnerLoader } from 'components/SpinnerLoader'
|
||||
import { useIsDarkMode } from 'hooks/use-theme'
|
||||
@@ -42,7 +44,7 @@ const HubDashboard = ({ path }: { path: string }) => {
|
||||
setVerificationInProgress(true)
|
||||
|
||||
try {
|
||||
const { verified, scriptContent: content } = await verifySignature(SCRIPT_URL, `${SCRIPT_URL}.sig`)
|
||||
const { verified, scriptContent: content } = await verifySignature(SCRIPT_URL, `${SCRIPT_URL}.sig`, PUBLIC_KEY)
|
||||
|
||||
if (!verified || !content) {
|
||||
setScriptError(true)
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export const PUBLIC_KEY = 'MCowBQYDK2VwAyEAWMBZ0pMBaL/s8gNXxpAPCIQ8bxjnuz6bQFwGYvjXDfg='
|
||||
@@ -3,9 +3,10 @@ import { ReactNode } from 'react'
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
||||
|
||||
import { useHubDemo } from './use-hub-demo'
|
||||
import verifySignature from './workers/scriptVerification'
|
||||
|
||||
vi.mock('./workers/scriptVerification', () => ({
|
||||
import verifySignature from 'utils/workers/scriptVerification'
|
||||
|
||||
vi.mock('utils/workers/scriptVerification', () => ({
|
||||
default: vi.fn(),
|
||||
}))
|
||||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { ReactNode, useEffect, useMemo, useState } from 'react'
|
||||
import { RouteObject } from 'react-router-dom'
|
||||
|
||||
import { PUBLIC_KEY } from './constants'
|
||||
|
||||
import HubDashboard from 'pages/hub-demo/HubDashboard'
|
||||
import { ApiIcon, DashboardIcon, GatewayIcon, PortalIcon } from 'pages/hub-demo/icons'
|
||||
import verifySignature from 'pages/hub-demo/workers/scriptVerification'
|
||||
import verifySignature from 'utils/workers/scriptVerification'
|
||||
|
||||
const ROUTES_MANIFEST_URL = 'https://traefik.github.io/hub-ui-demo-app/config/routes.json'
|
||||
|
||||
@@ -20,7 +22,11 @@ const useHubDemoRoutesManifest = (): HubDemo.Manifest | null => {
|
||||
useEffect(() => {
|
||||
const fetchManifest = async () => {
|
||||
try {
|
||||
const { verified, scriptContent } = await verifySignature(ROUTES_MANIFEST_URL, `${ROUTES_MANIFEST_URL}.sig`)
|
||||
const { verified, scriptContent } = await verifySignature(
|
||||
ROUTES_MANIFEST_URL,
|
||||
`${ROUTES_MANIFEST_URL}.sig`,
|
||||
PUBLIC_KEY,
|
||||
)
|
||||
|
||||
if (!verified || !scriptContent) {
|
||||
setManifest(null)
|
||||
|
||||
@@ -14,7 +14,7 @@ import SortableTh from 'components/tables/SortableTh'
|
||||
import Tooltip from 'components/Tooltip'
|
||||
import TooltipText from 'components/TooltipText'
|
||||
import useFetchWithPagination, { pagesResponseInterface, RenderRowType } from 'hooks/use-fetch-with-pagination'
|
||||
import { EmptyPlaceholder } from 'layout/EmptyPlaceholder'
|
||||
import { EmptyPlaceholderTd } from 'layout/EmptyPlaceholder'
|
||||
import { parseMiddlewareType } from 'libs/parsers'
|
||||
|
||||
export const makeRowRender = (): RenderRowType => {
|
||||
@@ -79,9 +79,7 @@ export const TcpMiddlewaresRender = ({
|
||||
{(isEmpty || !!error) && (
|
||||
<AriaTfoot>
|
||||
<AriaTr>
|
||||
<AriaTd fullColSpan>
|
||||
<EmptyPlaceholder message={error ? 'Failed to fetch data' : 'No data available'} />
|
||||
</AriaTd>
|
||||
<EmptyPlaceholderTd message={error ? 'Failed to fetch data' : 'No data available'} />
|
||||
</AriaTr>
|
||||
</AriaTfoot>
|
||||
)}
|
||||
|
||||
@@ -16,7 +16,7 @@ import SortableTh from 'components/tables/SortableTh'
|
||||
import Tooltip from 'components/Tooltip'
|
||||
import TooltipText from 'components/TooltipText'
|
||||
import useFetchWithPagination, { pagesResponseInterface, RenderRowType } from 'hooks/use-fetch-with-pagination'
|
||||
import { EmptyPlaceholder } from 'layout/EmptyPlaceholder'
|
||||
import { EmptyPlaceholderTd } from 'layout/EmptyPlaceholder'
|
||||
|
||||
export const makeRowRender = (): RenderRowType => {
|
||||
const TcpRoutersRenderRow = (row) => (
|
||||
@@ -96,9 +96,7 @@ export const TcpRoutersRender = ({
|
||||
{(isEmpty || !!error) && (
|
||||
<AriaTfoot>
|
||||
<AriaTr>
|
||||
<AriaTd fullColSpan>
|
||||
<EmptyPlaceholder message={error ? 'Failed to fetch data' : 'No data available'} />
|
||||
</AriaTd>
|
||||
<EmptyPlaceholderTd message={error ? 'Failed to fetch data' : 'No data available'} />
|
||||
</AriaTr>
|
||||
</AriaTfoot>
|
||||
)}
|
||||
|
||||
@@ -14,7 +14,7 @@ import SortableTh from 'components/tables/SortableTh'
|
||||
import Tooltip from 'components/Tooltip'
|
||||
import TooltipText from 'components/TooltipText'
|
||||
import useFetchWithPagination, { pagesResponseInterface, RenderRowType } from 'hooks/use-fetch-with-pagination'
|
||||
import { EmptyPlaceholder } from 'layout/EmptyPlaceholder'
|
||||
import { EmptyPlaceholderTd } from 'layout/EmptyPlaceholder'
|
||||
|
||||
export const makeRowRender = (): RenderRowType => {
|
||||
const TcpServicesRenderRow = (row) => (
|
||||
@@ -78,9 +78,7 @@ export const TcpServicesRender = ({
|
||||
{(isEmpty || !!error) && (
|
||||
<AriaTfoot>
|
||||
<AriaTr>
|
||||
<AriaTd fullColSpan>
|
||||
<EmptyPlaceholder message={error ? 'Failed to fetch data' : 'No data available'} />
|
||||
</AriaTd>
|
||||
<EmptyPlaceholderTd message={error ? 'Failed to fetch data' : 'No data available'} />
|
||||
</AriaTr>
|
||||
</AriaTfoot>
|
||||
)}
|
||||
|
||||
@@ -15,7 +15,7 @@ import SortableTh from 'components/tables/SortableTh'
|
||||
import Tooltip from 'components/Tooltip'
|
||||
import TooltipText from 'components/TooltipText'
|
||||
import useFetchWithPagination, { pagesResponseInterface, RenderRowType } from 'hooks/use-fetch-with-pagination'
|
||||
import { EmptyPlaceholder } from 'layout/EmptyPlaceholder'
|
||||
import { EmptyPlaceholderTd } from 'layout/EmptyPlaceholder'
|
||||
|
||||
export const makeRowRender = (): RenderRowType => {
|
||||
const UdpRoutersRenderRow = (row) => (
|
||||
@@ -81,9 +81,7 @@ export const UdpRoutersRender = ({
|
||||
{(isEmpty || !!error) && (
|
||||
<AriaTfoot>
|
||||
<AriaTr>
|
||||
<AriaTd fullColSpan>
|
||||
<EmptyPlaceholder message={error ? 'Failed to fetch data' : 'No data available'} />
|
||||
</AriaTd>
|
||||
<EmptyPlaceholderTd message={error ? 'Failed to fetch data' : 'No data available'} />
|
||||
</AriaTr>
|
||||
</AriaTfoot>
|
||||
)}
|
||||
|
||||
@@ -14,7 +14,7 @@ import SortableTh from 'components/tables/SortableTh'
|
||||
import Tooltip from 'components/Tooltip'
|
||||
import TooltipText from 'components/TooltipText'
|
||||
import useFetchWithPagination, { pagesResponseInterface, RenderRowType } from 'hooks/use-fetch-with-pagination'
|
||||
import { EmptyPlaceholder } from 'layout/EmptyPlaceholder'
|
||||
import { EmptyPlaceholderTd } from 'layout/EmptyPlaceholder'
|
||||
|
||||
export const makeRowRender = (): RenderRowType => {
|
||||
const UdpServicesRenderRow = (row) => (
|
||||
@@ -78,9 +78,7 @@ export const UdpServicesRender = ({
|
||||
{(isEmpty || !!error) && (
|
||||
<AriaTfoot>
|
||||
<AriaTr>
|
||||
<AriaTd fullColSpan>
|
||||
<EmptyPlaceholder message={error ? 'Failed to fetch data' : 'No data available'} />
|
||||
</AriaTd>
|
||||
<EmptyPlaceholderTd message={error ? 'Failed to fetch data' : 'No data available'} />
|
||||
</AriaTr>
|
||||
</AriaTfoot>
|
||||
)}
|
||||
|
||||
+7
-15
@@ -2,6 +2,8 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
||||
|
||||
import verifySignature from './scriptVerification'
|
||||
|
||||
const SCRIPT_PATH = 'https://example.com/script.js'
|
||||
const MOCK_PUBLIC_KEY = 'MCowBQYDK2VwAyEAWH71OHphISjNK3mizCR/BawiDxc6IXT1vFHpBcxSIA0='
|
||||
class MockWorker {
|
||||
onmessage: ((event: MessageEvent) => void) | null = null
|
||||
onerror: ((error: ErrorEvent) => void) | null = null
|
||||
@@ -46,17 +48,14 @@ describe('verifySignature', () => {
|
||||
})
|
||||
|
||||
it('should return true when verification succeeds', async () => {
|
||||
const scriptPath = 'https://example.com/script.js'
|
||||
const signaturePath = 'https://example.com/script.js.sig'
|
||||
|
||||
const promise = verifySignature(scriptPath, signaturePath)
|
||||
const promise = verifySignature(SCRIPT_PATH, `${SCRIPT_PATH}.sig`, MOCK_PUBLIC_KEY)
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 0))
|
||||
|
||||
expect(mockWorkerInstance.postMessage).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
scriptUrl: scriptPath,
|
||||
signatureUrl: signaturePath,
|
||||
scriptUrl: SCRIPT_PATH,
|
||||
signatureUrl: `${SCRIPT_PATH}.sig`,
|
||||
requestId: expect.any(String),
|
||||
}),
|
||||
)
|
||||
@@ -76,12 +75,9 @@ describe('verifySignature', () => {
|
||||
})
|
||||
|
||||
it('should return false when verification fails', async () => {
|
||||
const scriptPath = 'https://example.com/script.js'
|
||||
const signaturePath = 'https://example.com/script.js.sig'
|
||||
|
||||
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
||||
|
||||
const promise = verifySignature(scriptPath, signaturePath)
|
||||
const promise = verifySignature(SCRIPT_PATH, `${SCRIPT_PATH}.sig`, MOCK_PUBLIC_KEY)
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 0))
|
||||
|
||||
@@ -101,16 +97,12 @@ describe('verifySignature', () => {
|
||||
})
|
||||
|
||||
it('should return false when worker throws an error', async () => {
|
||||
const scriptPath = 'https://example.com/script.js'
|
||||
const signaturePath = 'https://example.com/script.js.sig'
|
||||
|
||||
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
||||
|
||||
const promise = verifySignature(scriptPath, signaturePath)
|
||||
const promise = verifySignature(SCRIPT_PATH, `${SCRIPT_PATH}.sig`, MOCK_PUBLIC_KEY)
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 0))
|
||||
|
||||
// Simulate worker onerror event
|
||||
const error = new Error('Worker crashed')
|
||||
mockWorkerInstance.simulateError(error)
|
||||
|
||||
+1
-3
@@ -3,12 +3,10 @@ export interface VerificationResult {
|
||||
scriptContent?: ArrayBuffer
|
||||
}
|
||||
|
||||
const PUBLIC_KEY = 'MCowBQYDK2VwAyEAWMBZ0pMBaL/s8gNXxpAPCIQ8bxjnuz6bQFwGYvjXDfg='
|
||||
|
||||
async function verifySignature(
|
||||
contentPath: string,
|
||||
signaturePath: string,
|
||||
publicKey: string = PUBLIC_KEY,
|
||||
publicKey: string,
|
||||
): Promise<VerificationResult> {
|
||||
return new Promise((resolve) => {
|
||||
const requestId = Math.random().toString(36).substring(2)
|
||||
+41
-41
@@ -2871,24 +2871,24 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@microsoft/api-extractor-model@npm:7.31.3":
|
||||
version: 7.31.3
|
||||
resolution: "@microsoft/api-extractor-model@npm:7.31.3"
|
||||
"@microsoft/api-extractor-model@npm:7.32.0":
|
||||
version: 7.32.0
|
||||
resolution: "@microsoft/api-extractor-model@npm:7.32.0"
|
||||
dependencies:
|
||||
"@microsoft/tsdoc": "npm:~0.15.1"
|
||||
"@microsoft/tsdoc-config": "npm:~0.17.1"
|
||||
"@microsoft/tsdoc": "npm:~0.16.0"
|
||||
"@microsoft/tsdoc-config": "npm:~0.18.0"
|
||||
"@rushstack/node-core-library": "npm:5.18.0"
|
||||
checksum: 10c0/4e4a798c5d92b72fa664932019563f085153cf33f7745f8ea452901348a0021f19c7c3db55e5555b779a78df52d93ec10960349b5bc1ee53bf555e63c0fe1197
|
||||
checksum: 10c0/e6d9c54a457c66dec53765522f411c8ca5d2bb8c51559b9f952fdd7bb88b10efe035a8dbdf8b672644c136d75a65f3446b3b18938fa50c0a766f83111cbda5eb
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@microsoft/api-extractor@npm:^7.50.1":
|
||||
version: 7.54.0
|
||||
resolution: "@microsoft/api-extractor@npm:7.54.0"
|
||||
version: 7.55.0
|
||||
resolution: "@microsoft/api-extractor@npm:7.55.0"
|
||||
dependencies:
|
||||
"@microsoft/api-extractor-model": "npm:7.31.3"
|
||||
"@microsoft/tsdoc": "npm:~0.15.1"
|
||||
"@microsoft/tsdoc-config": "npm:~0.17.1"
|
||||
"@microsoft/api-extractor-model": "npm:7.32.0"
|
||||
"@microsoft/tsdoc": "npm:~0.16.0"
|
||||
"@microsoft/tsdoc-config": "npm:~0.18.0"
|
||||
"@rushstack/node-core-library": "npm:5.18.0"
|
||||
"@rushstack/rig-package": "npm:0.6.0"
|
||||
"@rushstack/terminal": "npm:0.19.3"
|
||||
@@ -2902,26 +2902,26 @@ __metadata:
|
||||
typescript: "npm:5.8.2"
|
||||
bin:
|
||||
api-extractor: bin/api-extractor
|
||||
checksum: 10c0/e4708ac5edc3bb32988b632cc75e6f5e5b3afe7772c7229974db91f731d6b8d3c786c406d5437bfce893b78c6f23c64b084db52952a7d35294e2525171a465ee
|
||||
checksum: 10c0/3211981b7aaf6ca7a36fe33dc9cab5014dc753c0c75d09ace46bef07db947a433ca9daecc843ea13b29fe7527ea3d357c7bd5051fedf10ae3b3db31d2a5de71f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@microsoft/tsdoc-config@npm:~0.17.1":
|
||||
version: 0.17.1
|
||||
resolution: "@microsoft/tsdoc-config@npm:0.17.1"
|
||||
"@microsoft/tsdoc-config@npm:~0.18.0":
|
||||
version: 0.18.0
|
||||
resolution: "@microsoft/tsdoc-config@npm:0.18.0"
|
||||
dependencies:
|
||||
"@microsoft/tsdoc": "npm:0.15.1"
|
||||
"@microsoft/tsdoc": "npm:0.16.0"
|
||||
ajv: "npm:~8.12.0"
|
||||
jju: "npm:~1.4.0"
|
||||
resolve: "npm:~1.22.2"
|
||||
checksum: 10c0/a686355796f492f27af17e2a17d615221309caf4d9f9047a5a8f17f8625c467c4c81e2a7923ddafd71b892631d5e5013c4b8cc49c5867d3cc1d260fd90c1413d
|
||||
checksum: 10c0/6e2c3bfde3e5fa4c0360127c86fe016dcf1b09d0091d767c06ce916284d3f6aeea3617a33b855c5bb2615ab0f2840eeebd4c7f4a1f879f951828d213bf306cfd
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@microsoft/tsdoc@npm:0.15.1, @microsoft/tsdoc@npm:~0.15.1":
|
||||
version: 0.15.1
|
||||
resolution: "@microsoft/tsdoc@npm:0.15.1"
|
||||
checksum: 10c0/09948691fac56c45a0d1920de478d66a30371a325bd81addc92eea5654d95106ce173c440fea1a1bd5bb95b3a544b6d4def7bb0b5a846c05d043575d8369a20c
|
||||
"@microsoft/tsdoc@npm:0.16.0, @microsoft/tsdoc@npm:~0.16.0":
|
||||
version: 0.16.0
|
||||
resolution: "@microsoft/tsdoc@npm:0.16.0"
|
||||
checksum: 10c0/8883bb0ed22753af7360e9222687fda4eb448f0a574ea34b4596c11e320148b3ae0d24e00f8923df8ba7bc62a46a6f53b9343243a348640d923dfd55d52cd6bb
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -6524,26 +6524,26 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@vue/compiler-core@npm:3.5.23":
|
||||
version: 3.5.23
|
||||
resolution: "@vue/compiler-core@npm:3.5.23"
|
||||
"@vue/compiler-core@npm:3.5.24":
|
||||
version: 3.5.24
|
||||
resolution: "@vue/compiler-core@npm:3.5.24"
|
||||
dependencies:
|
||||
"@babel/parser": "npm:^7.28.5"
|
||||
"@vue/shared": "npm:3.5.23"
|
||||
"@vue/shared": "npm:3.5.24"
|
||||
entities: "npm:^4.5.0"
|
||||
estree-walker: "npm:^2.0.2"
|
||||
source-map-js: "npm:^1.2.1"
|
||||
checksum: 10c0/195c57b2eb8c6948bf3b1b3f65c2a5a9bf9e252376bcd22bd9b5e1787c4254abc4bffab5f15902c7820f5e607b26d44578cddeb39605ece37b611703c2d6152b
|
||||
checksum: 10c0/d5b1421c0c0cfdff6b6ae2ef3d59b5901f0fec8ad2fa153f5ae1ec8487b898c92766353c661f68b892580ab0eacbc493632c946af8141045d6e76d67797b8a84
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@vue/compiler-dom@npm:^3.5.0":
|
||||
version: 3.5.23
|
||||
resolution: "@vue/compiler-dom@npm:3.5.23"
|
||||
version: 3.5.24
|
||||
resolution: "@vue/compiler-dom@npm:3.5.24"
|
||||
dependencies:
|
||||
"@vue/compiler-core": "npm:3.5.23"
|
||||
"@vue/shared": "npm:3.5.23"
|
||||
checksum: 10c0/fb925b2d64de40c1b39852f5fd26fdec3238f8381ccc2b30a1bef372ef894fff4e6f0231f8a135a02d6a5c8b8254dc7018bcd136a689579a72a3a0e1ff211a89
|
||||
"@vue/compiler-core": "npm:3.5.24"
|
||||
"@vue/shared": "npm:3.5.24"
|
||||
checksum: 10c0/d49cb715f2e1cb2272ede2e41901282fb3f6fbdf489c8aa737e60c68e21216e07b72942695a80430fee8f11e5933e36fc90615b146b189cac925bf32f2727c95
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -6578,10 +6578,10 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@vue/shared@npm:3.5.23, @vue/shared@npm:^3.5.0":
|
||||
version: 3.5.23
|
||||
resolution: "@vue/shared@npm:3.5.23"
|
||||
checksum: 10c0/0f051ea60a756520b0b0af3d5058587b47f1942476c7f2cee6f78589c97c246acabdea11c73e2f84f13ecfb36c1160aacecca37694144326ebec8c108103bb89
|
||||
"@vue/shared@npm:3.5.24, @vue/shared@npm:^3.5.0":
|
||||
version: 3.5.24
|
||||
resolution: "@vue/shared@npm:3.5.24"
|
||||
checksum: 10c0/4fd5665539fa5be3d12280c1921a8db3a707115fef54d22d83ce347ea06e3b1089dfe07292e0c46bbebf23553c7c1ec98010972ebccf10532db82422801288ff
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -10099,9 +10099,9 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"exsolve@npm:^1.0.7":
|
||||
version: 1.0.7
|
||||
resolution: "exsolve@npm:1.0.7"
|
||||
checksum: 10c0/4479369d0bd84bb7e0b4f5d9bc18d26a89b6dbbbccd73f9d383d14892ef78ddbe159e01781055342f83dc00ebe90044036daf17ddf55cc21e2cac6609aa15631
|
||||
version: 1.0.8
|
||||
resolution: "exsolve@npm:1.0.8"
|
||||
checksum: 10c0/65e44ae05bd4a4a5d87cfdbbd6b8f24389282cf9f85fa5feb17ca87ad3f354877e6af4cd99e02fc29044174891f82d1d68c77f69234410eb8f163530e6278c67
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -18670,8 +18670,8 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"vite@npm:^5.0.0 || ^6.0.0 || ^7.0.0, vite@npm:^5.0.0 || ^6.0.0 || ^7.0.0-0":
|
||||
version: 7.2.1
|
||||
resolution: "vite@npm:7.2.1"
|
||||
version: 7.2.2
|
||||
resolution: "vite@npm:7.2.2"
|
||||
dependencies:
|
||||
esbuild: "npm:^0.25.0"
|
||||
fdir: "npm:^6.5.0"
|
||||
@@ -18720,7 +18720,7 @@ __metadata:
|
||||
optional: true
|
||||
bin:
|
||||
vite: bin/vite.js
|
||||
checksum: 10c0/25fbcfc67b1598fa6152f3ed0a7355144a2ac203859ad3b6a2e466b0930ec1081c19cc6f0d83104897517ecf30c0aac3e4a50c4e5e2980d3659decb1d9e41a28
|
||||
checksum: 10c0/9c76ee441f8dbec645ddaecc28d1f9cf35670ffa91cff69af7b1d5081545331603f0b1289d437b2fa8dc43cdc77b4d96b5bd9c9aed66310f490cb1a06f9c814c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
||||
Reference in New Issue
Block a user