-
Notifications
You must be signed in to change notification settings - Fork 6.2k
/
Copy pathaddBaseUrl.ts
96 lines (88 loc) · 3.17 KB
/
addBaseUrl.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
/*
* Copyright 2020 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { CompoundEntityRef } from '@backstage/catalog-model';
import { TechDocsStorageApi } from '../../api';
import type { Transformer } from './transformer';
type AddBaseUrlOptions = {
techdocsStorageApi: TechDocsStorageApi;
entityId: CompoundEntityRef;
path: string;
};
/**
* TechDocs backend serves SVGs with text/plain content-type for security. This
* helper determines if an SVG is being loaded from the backend, and thus needs
* inlining to be displayed properly.
*/
const isSvgNeedingInlining = (
attrName: string,
attrVal: string,
apiOrigin: string,
) => {
const isSrcToSvg = attrName === 'src' && attrVal.endsWith('.svg');
const isRelativeUrl = !attrVal.match(/^([a-z]*:)?\/\//i);
const pointsToOurBackend = attrVal.startsWith(apiOrigin);
return isSrcToSvg && (isRelativeUrl || pointsToOurBackend);
};
export const addBaseUrl = ({
techdocsStorageApi,
entityId,
path,
}: AddBaseUrlOptions): Transformer => {
return async dom => {
const apiOrigin = await techdocsStorageApi.getApiOrigin();
const updateDom = async <T extends Element>(
list: HTMLCollectionOf<T> | NodeListOf<T>,
attributeName: string,
) => {
for (const elem of list) {
if (elem.hasAttribute(attributeName)) {
const elemAttribute = elem.getAttribute(attributeName);
if (!elemAttribute) return;
// Special handling for SVG images.
const newValue = await techdocsStorageApi.getBaseUrl(
elemAttribute,
entityId,
path,
);
if (isSvgNeedingInlining(attributeName, elemAttribute, apiOrigin)) {
try {
const svg = await fetch(newValue, { credentials: 'include' });
const svgContent = await svg.text();
elem.setAttribute(
attributeName,
`data:image/svg+xml;base64,${btoa(
unescape(encodeURIComponent(svgContent)),
)}`,
);
} catch (e) {
elem.setAttribute('alt', `Error: ${elemAttribute}`);
}
} else {
elem.setAttribute(attributeName, newValue);
}
}
}
};
await Promise.all([
updateDom<HTMLImageElement>(dom.querySelectorAll('img'), 'src'),
updateDom<HTMLScriptElement>(dom.querySelectorAll('script'), 'src'),
updateDom<HTMLSourceElement>(dom.querySelectorAll('source'), 'src'),
updateDom<HTMLLinkElement>(dom.querySelectorAll('link'), 'href'),
updateDom<HTMLAnchorElement>(dom.querySelectorAll('a[download]'), 'href'),
]);
return dom;
};
};