227 lines
6.0 KiB
JavaScript
227 lines
6.0 KiB
JavaScript
export const getAssetFromGroup = (assets, assetType) => {
|
|
if (!assetType || !assets) {
|
|
return null;
|
|
}
|
|
|
|
if (Array.isArray(assets)) {
|
|
return assets.find((entry) => entry?.asset_type === assetType) || null;
|
|
}
|
|
|
|
return assets?.[assetType] || null;
|
|
};
|
|
|
|
export const getAssetFromVersion = (currentVersion, assetType) => {
|
|
if (!currentVersion) {
|
|
return null;
|
|
}
|
|
return getAssetFromGroup(currentVersion.assets, assetType);
|
|
};
|
|
|
|
export const resolveDocumentAssetUrl = (doc, type, { ensureAssetUrl, getAsset, ensureOptions } = {}) => {
|
|
if (!doc || !type) {
|
|
return null;
|
|
}
|
|
const asset = typeof getAsset === 'function' ? getAsset(doc, type) : null;
|
|
if (!asset) {
|
|
return null;
|
|
}
|
|
const now = Date.now();
|
|
const expiresAt = typeof asset.expiresAt === 'number' ? asset.expiresAt : null;
|
|
const hasFreshUrl = asset.url && (!expiresAt || expiresAt > now);
|
|
if (hasFreshUrl) {
|
|
return asset.url;
|
|
}
|
|
if (doc.id && asset.id && typeof ensureAssetUrl === 'function') {
|
|
const force = Boolean(asset.url && expiresAt && expiresAt <= now);
|
|
const options = ensureOptions ? { ...ensureOptions, force } : { force };
|
|
ensureAssetUrl(doc.id, asset, options).catch(() => {});
|
|
}
|
|
return null;
|
|
};
|
|
|
|
class AssetManager {
|
|
constructor({ api, assetPresignTtlMs }) {
|
|
this.api = api;
|
|
this.assetPresignTtlMs = assetPresignTtlMs;
|
|
this.assetCache = new Map();
|
|
this.assetInflight = new Map();
|
|
}
|
|
|
|
setApi(api) {
|
|
this.api = api;
|
|
}
|
|
|
|
rememberAsset(entry) {
|
|
if (entry?.id) {
|
|
this.assetCache.set(entry.id, entry);
|
|
}
|
|
}
|
|
|
|
hydrateAsset(asset) {
|
|
if (!asset || !asset.id) {
|
|
return asset;
|
|
}
|
|
const cached = this.assetCache.get(asset.id);
|
|
if (!cached) {
|
|
return asset;
|
|
}
|
|
const merged = { ...cached, ...asset };
|
|
if (cached.url && !asset.url) {
|
|
merged.url = cached.url;
|
|
}
|
|
if (cached.expiresAt) {
|
|
const cachedExpires = Number(cached.expiresAt) || null;
|
|
const assetExpires = Number(asset.expiresAt) || null;
|
|
if (!assetExpires || (cachedExpires && cachedExpires > assetExpires)) {
|
|
merged.expiresAt = cachedExpires;
|
|
}
|
|
}
|
|
return merged;
|
|
}
|
|
|
|
hydrateDocument(document) {
|
|
if (!document) {
|
|
return document;
|
|
}
|
|
|
|
const currentVersion = document.current_version || null;
|
|
if (!currentVersion) {
|
|
return document;
|
|
}
|
|
|
|
let changed = false;
|
|
let nextAssets = currentVersion.assets;
|
|
|
|
if (nextAssets && !Array.isArray(nextAssets)) {
|
|
const hydrated = {};
|
|
Object.keys(nextAssets).forEach((key) => {
|
|
hydrated[key] = this.hydrateAsset(nextAssets[key]);
|
|
if (hydrated[key] !== nextAssets[key]) {
|
|
changed = true;
|
|
}
|
|
});
|
|
if (changed) {
|
|
nextAssets = { ...nextAssets, ...hydrated };
|
|
}
|
|
} else if (Array.isArray(nextAssets)) {
|
|
const hydratedList = nextAssets.map((item) => this.hydrateAsset(item));
|
|
if (
|
|
hydratedList.length !== nextAssets.length ||
|
|
hydratedList.some((item, index) => item !== nextAssets[index])
|
|
) {
|
|
changed = true;
|
|
nextAssets = hydratedList;
|
|
}
|
|
}
|
|
|
|
if (!changed) {
|
|
return document;
|
|
}
|
|
|
|
const nextCurrentVersion = { ...currentVersion, assets: nextAssets };
|
|
return { ...document, current_version: nextCurrentVersion };
|
|
}
|
|
|
|
hydrateDocuments(documents) {
|
|
if (!Array.isArray(documents)) {
|
|
return documents;
|
|
}
|
|
return documents.map((doc) => this.hydrateDocument(doc));
|
|
}
|
|
|
|
hydrateDetail(detail) {
|
|
if (!detail) {
|
|
return detail;
|
|
}
|
|
let changed = false;
|
|
const next = { ...detail };
|
|
|
|
if (detail.document) {
|
|
const hydratedDocument = this.hydrateDocument(detail.document);
|
|
if (hydratedDocument !== detail.document) {
|
|
next.document = hydratedDocument;
|
|
changed = true;
|
|
}
|
|
}
|
|
|
|
if (Array.isArray(detail.assets)) {
|
|
const hydratedAssets = detail.assets.map((item) => this.hydrateAsset(item));
|
|
if (
|
|
hydratedAssets.length !== detail.assets.length ||
|
|
hydratedAssets.some((item, index) => item !== detail.assets[index])
|
|
) {
|
|
next.assets = hydratedAssets;
|
|
changed = true;
|
|
}
|
|
}
|
|
|
|
return changed ? next : detail;
|
|
}
|
|
|
|
hydrateFolderContents(contents) {
|
|
if (!contents) {
|
|
return contents;
|
|
}
|
|
const next = { ...contents };
|
|
if (Array.isArray(contents.documents)) {
|
|
next.documents = this.hydrateDocuments(contents.documents);
|
|
}
|
|
if (contents.document) {
|
|
next.document = this.hydrateDocument(contents.document);
|
|
}
|
|
return next;
|
|
}
|
|
|
|
ensureAsset(documentId, asset, { force = false } = {}) {
|
|
if (!documentId || !asset?.id) {
|
|
return Promise.resolve(asset || null);
|
|
}
|
|
|
|
const assetExpiresAt = typeof asset.expiresAt === 'number' ? asset.expiresAt : null;
|
|
if (!force && asset?.url && (!assetExpiresAt || assetExpiresAt > Date.now())) {
|
|
this.rememberAsset(asset);
|
|
return Promise.resolve(asset);
|
|
}
|
|
|
|
const cached = this.assetCache.get(asset.id);
|
|
const now = Date.now();
|
|
if (!force && cached && cached.expiresAt && cached.expiresAt > now && cached.url) {
|
|
return Promise.resolve({ ...asset, ...cached });
|
|
}
|
|
|
|
const inflightKey = `${documentId}:${asset.id}`;
|
|
if (!force && this.assetInflight.has(inflightKey)) {
|
|
return this.assetInflight.get(inflightKey);
|
|
}
|
|
|
|
if (!this.api) {
|
|
return Promise.reject(new Error('AssetManager API client is not configured.'));
|
|
}
|
|
|
|
const request = this.api
|
|
.get(`/documents/${documentId}/assets/${asset.id}`)
|
|
.then(({ data }) => {
|
|
const entry = {
|
|
...asset,
|
|
...data,
|
|
expiresAt: Date.now() + this.assetPresignTtlMs,
|
|
};
|
|
this.rememberAsset(entry);
|
|
return entry;
|
|
})
|
|
.finally(() => {
|
|
this.assetInflight.delete(inflightKey);
|
|
});
|
|
|
|
this.assetInflight.set(inflightKey, request);
|
|
return request;
|
|
}
|
|
|
|
reset() {
|
|
this.assetCache.clear();
|
|
this.assetInflight.clear();
|
|
}
|
|
}
|
|
|
|
export default AssetManager;
|