-
Notifications
You must be signed in to change notification settings - Fork 199
/
Copy pathxhr.js
136 lines (122 loc) · 4.02 KB
/
xhr.js
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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
/*
* Copyright (c) 2017 Digital Bazaar, Inc. All rights reserved.
*/
'use strict';
const contentType = require('content-type');
const {
parseLinkHeader,
buildHeaders
} = require('../util');
const {LINK_HEADER_CONTEXT} = require('../constants');
const JsonLdError = require('../JsonLdError');
const RequestQueue = require('../RequestQueue');
const {prependBase} = require('../url');
const REGEX_LINK_HEADER = /(^|(\r\n))link:/i;
/**
* Creates a built-in XMLHttpRequest document loader.
*
* @param options the options to use:
* secure: require all URLs to use HTTPS.
* headers: an object (map) of headers which will be passed as request
* headers for the requested document. Accept is not allowed.
* [xhr]: the XMLHttpRequest API to use.
*
* @return the XMLHttpRequest document loader.
*/
module.exports = ({
secure,
headers = {},
xhr
} = {headers: {}}) => {
headers = buildHeaders(headers);
const queue = new RequestQueue();
return queue.wrapLoader(loader);
async function loader(url, options) {
if(url.indexOf('http:') !== 0 && url.indexOf('https:') !== 0) {
throw new JsonLdError(
'URL could not be dereferenced; only "http" and "https" URLs are ' +
'supported.',
'jsonld.InvalidUrl', {code: 'loading document failed', url});
}
if(secure && url.indexOf('https') !== 0) {
throw new JsonLdError(
'URL could not be dereferenced; secure mode is enabled and ' +
'the URL\'s scheme is not "https".',
'jsonld.InvalidUrl', {code: 'loading document failed', url});
}
// add any optional requestProfile
if(options.requestProfile) {
headers.Accept =
headers.Accept + `, application/ld+json;profile=${options.requestProfile}`;
}
let req;
try {
req = await _get(xhr, url, headers);
} catch(e) {
throw new JsonLdError(
'URL could not be dereferenced, an error occurred.',
'jsonld.LoadDocumentError',
{code: 'loading document failed', url, cause: e});
}
if(req.status >= 400) {
throw new JsonLdError(
'URL could not be dereferenced: ' + req.statusText,
'jsonld.LoadDocumentError', {
code: 'loading document failed',
url,
httpStatusCode: req.status
});
}
const {type, parameters} = contentType.parse(req);
let doc = {
contextUrl: null,
documentUrl: url,
document: req.response,
contentType: type,
profile: parameters.profile
};
let alternate = null;
// handle Link Header (avoid unsafe header warning by existence testing)
let linkHeader;
if(contentType !== 'application/ld+json' &&
REGEX_LINK_HEADER.test(req.getAllResponseHeaders())) {
linkHeader = req.getResponseHeader('Link');
}
if(linkHeader && contentType !== 'application/ld+json') {
// only 1 related link header permitted
const linkHeaders = parseLinkHeader(req.headers.link);
const linkedContext = linkHeaders[LINK_HEADER_CONTEXT];
if(Array.isArray(linkedContext)) {
throw new JsonLdError(
'URL could not be dereferenced, it has more than one ' +
'associated HTTP Link Header.',
'jsonld.InvalidUrl',
{code: 'multiple context link headers', url});
}
if(linkedContext) {
doc.contextUrl = linkedContext.target;
}
// "alternate" link header is a redirect
alternate = linkHeaders['alternate'];
if(alternate &&
alternate.type == 'application/ld+json' &&
!(contentType || '').match(/^application\/(\w*\+)?json$/)) {
doc = await loader(prependBase(url, alternate.target));
}
}
return doc;
}
};
function _get(xhr, url, headers) {
xhr = xhr || XMLHttpRequest;
const req = new xhr();
return new Promise((resolve, reject) => {
req.onload = () => resolve(req);
req.onerror = err => reject(err);
req.open('GET', url, true);
for(const k in headers) {
req.setRequestHeader(k, headers[k]);
}
req.send();
});
}