1
0
Fork 0

Restore remote Upgrade to Hub button web component

This commit is contained in:
Gina A. 2025-11-12 12:16:06 +01:00 committed by GitHub
parent 77b1282570
commit a01c73d506
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 1642 additions and 303 deletions

View file

@ -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')
})
})

View file

@ -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