126 lines
4.4 KiB
JavaScript
126 lines
4.4 KiB
JavaScript
// Transport-only lookup. Review eligibility continues to belong to the story
|
|
// and release manifests; an HTTPS URL never confers audio approval.
|
|
const manifest = require('./cosMediaManifest')
|
|
const paths = Object.create(null)
|
|
const urls = Object.create(null)
|
|
const SHARE_ASSET_ID = 'image.tang.share-preview'
|
|
|
|
for (const entry of manifest.entries) {
|
|
paths[entry.sourcePath] = entry
|
|
urls[entry.url] = entry
|
|
}
|
|
|
|
function entryFor(value) {
|
|
if (typeof value !== 'string') return null
|
|
if (urls[value]) return urls[value]
|
|
let relative = value.replace(/^\//, '')
|
|
if (relative.startsWith(`${manifest.namespace}/`)) {
|
|
relative = relative.slice(manifest.namespace.length + 1)
|
|
}
|
|
return paths[relative] || null
|
|
}
|
|
|
|
function resolve(value) {
|
|
const entry = entryFor(value)
|
|
return entry ? entry.url : ''
|
|
}
|
|
|
|
function verifiedUrl(value, sha256, kind) {
|
|
const entry = urls[value]
|
|
return entry && entry.sha256 === sha256 && entry.kind === kind
|
|
? entry.url : ''
|
|
}
|
|
|
|
function mapMedia(value) {
|
|
if (typeof value === 'string') {
|
|
if (/^\/(?:[a-z0-9-]+\/)?(?:assets|package-[a-z0-9-]+)\/.*\.(?:jpe?g|png|webp|gif|svg|avif|mp3|wav|aac|m4a|ogg|mp4|webm|mov)$/i.test(value)) {
|
|
// Unregistered declarations remain text-only; never guess an object URL.
|
|
return resolve(value)
|
|
}
|
|
return value
|
|
}
|
|
if (Array.isArray(value)) return value.map(mapMedia)
|
|
if (!value || typeof value !== 'object') return value
|
|
const mapped = {}
|
|
Object.keys(value).forEach(key => { mapped[key] = mapMedia(value[key]) })
|
|
return mapped
|
|
}
|
|
|
|
function beginComicImage(page, pageData) {
|
|
const previous = page._cosComicImage
|
|
if (!previous || previous.pageId !== pageData.currentPageId) {
|
|
page._cosComicImage = {
|
|
pageId: pageData.currentPageId,
|
|
generation: (previous ? previous.generation : 0) + 1,
|
|
failed: Object.create(null),
|
|
lastPatch: null,
|
|
}
|
|
}
|
|
const state = page._cosComicImage
|
|
pageData.comicImageGeneration = state.generation
|
|
// Ordinary reader interactions re-render this page. They must not revive a
|
|
// URL that already failed or replace terminal text fallback with an image.
|
|
if (state.lastPatch) Object.assign(pageData, state.lastPatch)
|
|
}
|
|
|
|
function isCurrentComicImageEvent(page, event) {
|
|
const state = page._cosComicImage
|
|
const dataset = event && event.currentTarget && event.currentTarget.dataset
|
|
return Boolean(state && dataset && !page.__tangDead && !page.__tangOriginalUnloaded
|
|
&& page.__tangVisible !== false && page.data.comicImageSrc
|
|
&& state.pageId === page.data.currentPageId
|
|
&& dataset.cosPageId === state.pageId
|
|
&& Number(dataset.cosImageGeneration) === state.generation
|
|
&& dataset.cosImageSrc === page.data.comicImageSrc)
|
|
}
|
|
|
|
function recordComicImageError(page, event) {
|
|
if (!isCurrentComicImageEvent(page, event)) return false
|
|
const state = page._cosComicImage
|
|
const src = page.data.comicImageSrc
|
|
if (state.failed[src]) return false
|
|
state.failed[src] = true
|
|
return true
|
|
}
|
|
|
|
function canUseComicFallback(page, src) {
|
|
const state = page._cosComicImage
|
|
const entry = entryFor(src)
|
|
return Boolean(state && entry && entry.kind === 'image' && !state.failed[src])
|
|
}
|
|
|
|
function applyComicImageFallback(page, patch) {
|
|
if (page._cosComicImage) page._cosComicImage.lastPatch = { ...patch }
|
|
page.setData(patch)
|
|
}
|
|
|
|
function prepareSharePreview(page, assetManager) {
|
|
if (page._sharePreviewPromise) return page._sharePreviewPromise
|
|
const generation = page.__tangShowGeneration
|
|
const alive = () => !page.__tangDead && !page.__tangOriginalUnloaded
|
|
&& page.__tangVisible !== false && page.__tangShowGeneration === generation
|
|
// Resolve again for each share; the manager rehashes cached bytes before use.
|
|
const promise = Promise.resolve().then(() => assetManager.resolve(SHARE_ASSET_ID))
|
|
.then(result => {
|
|
if (!alive()) return ''
|
|
const localPath = result && result.available && result.uri || ''
|
|
page.setData({ sharePreviewLocalPath: localPath })
|
|
return localPath
|
|
})
|
|
.catch(() => {
|
|
if (alive()) page.setData({ sharePreviewLocalPath: '' })
|
|
return ''
|
|
})
|
|
.finally(() => {
|
|
if (page._sharePreviewPromise === promise) page._sharePreviewPromise = null
|
|
})
|
|
page._sharePreviewPromise = promise
|
|
return promise
|
|
}
|
|
|
|
module.exports = {
|
|
SHARE_ASSET_ID, entryFor, resolve, verifiedUrl, mapMedia, prepareSharePreview,
|
|
beginComicImage, isCurrentComicImageEvent, recordComicImageError,
|
|
canUseComicFallback, applyComicImageFallback,
|
|
}
|