Feature(challenges): Load and cache required files
This commit is contained in:
@ -21,22 +21,27 @@ function createFileStream(files = {}) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const jQuery = {
|
const globalRequires = [{
|
||||||
|
link: 'https://cdnjs.cloudflare.com/' +
|
||||||
|
'ajax/libs/normalize/4.2.0/normalize.min.css'
|
||||||
|
}, {
|
||||||
src: '/bower_components/jquery/dist/jquery.js',
|
src: '/bower_components/jquery/dist/jquery.js',
|
||||||
script: true,
|
script: true,
|
||||||
type: 'global'
|
type: 'global',
|
||||||
};
|
crossDomain: false
|
||||||
|
}];
|
||||||
|
|
||||||
const scriptCache = new Map();
|
const scriptCache = new Map();
|
||||||
|
const linkCache = new Map();
|
||||||
|
|
||||||
function cacheScript({ src } = {}) {
|
function cacheScript({ src } = {}, crossDomain = true) {
|
||||||
if (!src) {
|
if (!src) {
|
||||||
return Observable.throw(new Error('No source provided for script'));
|
return Observable.throw(new Error('No source provided for script'));
|
||||||
}
|
}
|
||||||
if (scriptCache.has(src)) {
|
if (scriptCache.has(src)) {
|
||||||
return scriptCache.get(src);
|
return scriptCache.get(src);
|
||||||
}
|
}
|
||||||
const script$ = ajax$(src)
|
const script$ = ajax$({ url: src, crossDomain })
|
||||||
.doOnNext(res => {
|
.doOnNext(res => {
|
||||||
if (res.status !== 200) {
|
if (res.status !== 200) {
|
||||||
throw new Error('Request errror: ' + res.status);
|
throw new Error('Request errror: ' + res.status);
|
||||||
@ -51,12 +56,37 @@ function cacheScript({ src } = {}) {
|
|||||||
return script$;
|
return script$;
|
||||||
}
|
}
|
||||||
|
|
||||||
const frameRunner$ = cacheScript({ src: '/js/frame-runner.js' });
|
function cacheLink({ link } = {}, crossDomain = true) {
|
||||||
|
if (!link) {
|
||||||
|
return Observable.throw(new Error('No source provided for link'));
|
||||||
|
}
|
||||||
|
if (linkCache.has(link)) {
|
||||||
|
return linkCache.get(link);
|
||||||
|
}
|
||||||
|
const link$ = ajax$({ url: link, crossDomain })
|
||||||
|
.doOnNext(res => {
|
||||||
|
if (res.status !== 200) {
|
||||||
|
throw new Error('Request errror: ' + res.status);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.map(({ response }) => response)
|
||||||
|
.map(script => `<style>${script}</style>`)
|
||||||
|
.catch(createErrorObservable)
|
||||||
|
.shareReplay();
|
||||||
|
|
||||||
|
linkCache.set(link, link$);
|
||||||
|
return link$;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const htmlCatch = '\n<!--fcc-->';
|
const htmlCatch = '\n<!--fcc-->';
|
||||||
const jsCatch = '\n;/*fcc*/';
|
const jsCatch = '\n;/*fcc*/';
|
||||||
|
|
||||||
export default function executeChallengeSaga(action$, getState) {
|
export default function executeChallengeSaga(action$, getState) {
|
||||||
|
const frameRunner$ = cacheScript(
|
||||||
|
{ src: '/js/frame-runner.js' },
|
||||||
|
false
|
||||||
|
);
|
||||||
return action$
|
return action$
|
||||||
.filter(({ type }) => (
|
.filter(({ type }) => (
|
||||||
type === types.executeChallenge ||
|
type === types.executeChallenge ||
|
||||||
@ -64,7 +94,8 @@ export default function executeChallengeSaga(action$, getState) {
|
|||||||
))
|
))
|
||||||
.debounce(750)
|
.debounce(750)
|
||||||
.flatMapLatest(({ type }) => {
|
.flatMapLatest(({ type }) => {
|
||||||
const { files, required = [ jQuery ] } = getState().challengesApp;
|
const { files, required = [] } = getState().challengesApp;
|
||||||
|
const finalRequires = [...required, ...globalRequires ];
|
||||||
return createFileStream(files)
|
return createFileStream(files)
|
||||||
::throwers()
|
::throwers()
|
||||||
::transformers()
|
::transformers()
|
||||||
@ -88,10 +119,13 @@ export default function executeChallengeSaga(action$, getState) {
|
|||||||
}, ''))
|
}, ''))
|
||||||
// add required scripts and links here
|
// add required scripts and links here
|
||||||
.flatMap(source => {
|
.flatMap(source => {
|
||||||
const head$ = Observable.from(required)
|
const head$ = Observable.from(finalRequires)
|
||||||
.flatMap(required => {
|
.flatMap(required => {
|
||||||
if (required.script) {
|
if (required.script) {
|
||||||
return cacheScript(required);
|
return cacheScript(required, required.crossDomain);
|
||||||
|
}
|
||||||
|
if (required.link) {
|
||||||
|
return cacheLink(required, required.crossDomain);
|
||||||
}
|
}
|
||||||
return Observable.just('');
|
return Observable.just('');
|
||||||
})
|
})
|
||||||
|
@ -15,7 +15,11 @@ if (process.env.NODE_ENV !== 'production') {
|
|||||||
export default function csp() {
|
export default function csp() {
|
||||||
return helmet.contentSecurityPolicy({
|
return helmet.contentSecurityPolicy({
|
||||||
directives: {
|
directives: {
|
||||||
defaultSrc: trusted.concat('*.optimizely.com'),
|
defaultSrc: trusted.concat([
|
||||||
|
'*.optimizely.com',
|
||||||
|
'https://*.cloudflare.com',
|
||||||
|
'*.cloudflare.com'
|
||||||
|
]),
|
||||||
scriptSrc: [
|
scriptSrc: [
|
||||||
"'unsafe-eval'",
|
"'unsafe-eval'",
|
||||||
"'unsafe-inline'",
|
"'unsafe-inline'",
|
||||||
|
Reference in New Issue
Block a user