1 import { wait } from '@root-helpers/utils'
2 import { Segment } from 'p2p-media-loader-core'
3 import { basename } from 'path'
5 type SegmentsJSON = { [filename: string]: string | { [byterange: string]: string } }
9 function segmentValidatorFactory (segmentsSha256Url: string, isLive: boolean) {
10 let segmentsJSON = fetchSha256Segments(segmentsSha256Url)
11 const regex = /bytes=(\d+)-(\d+)/
13 return async function segmentValidator (segment: Segment, _method: string, _peerId: string, retry = 1) {
14 // Wait for hash generation from the server
15 if (isLive) await wait(1000)
17 const filename = basename(segment.url)
19 const segmentValue = (await segmentsJSON)[filename]
21 if (!segmentValue && retry > maxRetries) {
22 throw new Error(`Unknown segment name ${filename} in segment validator`)
26 console.log('Refetching sha segments for %s.', filename)
30 segmentsJSON = fetchSha256Segments(segmentsSha256Url)
31 await segmentValidator(segment, _method, _peerId, retry + 1)
36 let hashShouldBe: string
39 if (typeof segmentValue === 'string') {
40 hashShouldBe = segmentValue
42 const captured = regex.exec(segment.range)
43 range = captured[1] + '-' + captured[2]
45 hashShouldBe = segmentValue[range]
48 if (hashShouldBe === undefined) {
49 throw new Error(`Unknown segment name ${filename}/${range} in segment validator`)
52 const calculatedSha = await sha256Hex(segment.data)
53 if (calculatedSha !== hashShouldBe) {
55 `Hashes does not correspond for segment ${filename}/${range}` +
56 `(expected: ${hashShouldBe} instead of ${calculatedSha})`
62 // ---------------------------------------------------------------------------
65 segmentValidatorFactory
68 // ---------------------------------------------------------------------------
70 function fetchSha256Segments (url: string) {
72 .then(res => res.json() as Promise<SegmentsJSON>)
74 console.error('Cannot get sha256 segments', err)
79 async function sha256Hex (data?: ArrayBuffer) {
80 if (!data) return undefined
82 if (window.crypto.subtle) {
83 return window.crypto.subtle.digest('SHA-256', data)
84 .then(data => bufferToHex(data))
87 // Fallback for non HTTPS context
88 const shaModule = await import('sha.js')
89 return new shaModule.sha256().update(Buffer.from(data)).digest('hex')
92 // Thanks: https://stackoverflow.com/a/53307879
93 function bufferToHex (buffer?: ArrayBuffer) {
94 if (!buffer) return ''
97 const h = '0123456789abcdef'
98 const o = new Uint8Array(buffer)
100 o.forEach((v: any) => s += h[ v >> 4 ] + h[ v & 15 ])