]>
Commit | Line | Data |
---|---|---|
210856a7 | 1 | import { wait } from '@root-helpers/utils' |
09209296 C |
2 | import { Segment } from 'p2p-media-loader-core' |
3 | import { basename } from 'path' | |
4 | ||
c6c0fa6c C |
5 | type SegmentsJSON = { [filename: string]: string | { [byterange: string]: string } } |
6 | ||
210856a7 C |
7 | const maxRetries = 3 |
8 | ||
09209296 | 9 | function segmentValidatorFactory (segmentsSha256Url: string) { |
c6c0fa6c | 10 | let segmentsJSON = fetchSha256Segments(segmentsSha256Url) |
4c280004 | 11 | const regex = /bytes=(\d+)-(\d+)/ |
09209296 | 12 | |
210856a7 | 13 | return async function segmentValidator (segment: Segment, retry = 1) { |
4c280004 | 14 | const filename = basename(segment.url) |
09209296 | 15 | |
c6c0fa6c C |
16 | const segmentValue = (await segmentsJSON)[filename] |
17 | ||
210856a7 | 18 | if (!segmentValue && retry > maxRetries) { |
c6c0fa6c C |
19 | throw new Error(`Unknown segment name ${filename} in segment validator`) |
20 | } | |
21 | ||
22 | if (!segmentValue) { | |
210856a7 C |
23 | await wait(1000) |
24 | ||
25 | console.log('Refetching sha segments for %s.', filename) | |
c6c0fa6c | 26 | |
c6c0fa6c | 27 | segmentsJSON = fetchSha256Segments(segmentsSha256Url) |
210856a7 C |
28 | await segmentValidator(segment, retry + 1) |
29 | ||
c6c0fa6c C |
30 | return |
31 | } | |
32 | ||
33 | let hashShouldBe: string | |
34 | let range = '' | |
35 | ||
36 | if (typeof segmentValue === 'string') { | |
37 | hashShouldBe = segmentValue | |
38 | } else { | |
39 | const captured = regex.exec(segment.range) | |
40 | range = captured[1] + '-' + captured[2] | |
41 | ||
42 | hashShouldBe = segmentValue[range] | |
43 | } | |
4c280004 | 44 | |
09209296 | 45 | if (hashShouldBe === undefined) { |
4c280004 | 46 | throw new Error(`Unknown segment name ${filename}/${range} in segment validator`) |
09209296 C |
47 | } |
48 | ||
6d61da4e | 49 | const calculatedSha = await sha256Hex(segment.data) |
09209296 | 50 | if (calculatedSha !== hashShouldBe) { |
4c280004 C |
51 | throw new Error( |
52 | `Hashes does not correspond for segment ${filename}/${range}` + | |
53 | `(expected: ${hashShouldBe} instead of ${calculatedSha})` | |
54 | ) | |
09209296 C |
55 | } |
56 | } | |
57 | } | |
58 | ||
59 | // --------------------------------------------------------------------------- | |
60 | ||
61 | export { | |
62 | segmentValidatorFactory | |
63 | } | |
64 | ||
65 | // --------------------------------------------------------------------------- | |
66 | ||
67 | function fetchSha256Segments (url: string) { | |
68 | return fetch(url) | |
c6c0fa6c | 69 | .then(res => res.json() as Promise<SegmentsJSON>) |
09209296 C |
70 | .catch(err => { |
71 | console.error('Cannot get sha256 segments', err) | |
72 | return {} | |
73 | }) | |
74 | } | |
75 | ||
6d61da4e | 76 | async function sha256Hex (data?: ArrayBuffer) { |
09209296 C |
77 | if (!data) return undefined |
78 | ||
6d61da4e C |
79 | if (window.crypto.subtle) { |
80 | return window.crypto.subtle.digest('SHA-256', data) | |
81 | .then(data => bufferToHex(data)) | |
82 | } | |
83 | ||
84 | // Fallback for non HTTPS context | |
85 | const shaModule = await import('sha.js') | |
86 | return new shaModule.sha256().update(Buffer.from(data)).digest('hex') | |
09209296 C |
87 | } |
88 | ||
89 | // Thanks: https://stackoverflow.com/a/53307879 | |
6d61da4e | 90 | function bufferToHex (buffer?: ArrayBuffer) { |
09209296 C |
91 | if (!buffer) return '' |
92 | ||
93 | let s = '' | |
94 | const h = '0123456789abcdef' | |
95 | const o = new Uint8Array(buffer) | |
96 | ||
97 | o.forEach((v: any) => s += h[ v >> 4 ] + h[ v & 15 ]) | |
98 | ||
99 | return s | |
100 | } |