You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
187 lines
8.7 KiB
187 lines
8.7 KiB
4 months ago
|
import { __assign, __awaiter, __generator } from "tslib";
|
||
|
import { responseIterator } from "./responseIterator.js";
|
||
|
import { throwServerError } from "../utils/index.js";
|
||
|
import { PROTOCOL_ERRORS_SYMBOL } from "../../errors/index.js";
|
||
|
import { isApolloPayloadResult } from "../../utilities/common/incrementalResult.js";
|
||
|
var hasOwnProperty = Object.prototype.hasOwnProperty;
|
||
|
export function readMultipartBody(response, nextValue) {
|
||
|
var _a;
|
||
|
return __awaiter(this, void 0, void 0, function () {
|
||
|
var decoder, contentType, delimiter, boundaryVal, boundary, buffer, iterator, running, _b, value, done, chunk, searchFrom, bi, message, i, headers, contentType_1, body, result, next;
|
||
|
var _c, _d;
|
||
|
return __generator(this, function (_e) {
|
||
|
switch (_e.label) {
|
||
|
case 0:
|
||
|
if (TextDecoder === undefined) {
|
||
|
throw new Error("TextDecoder must be defined in the environment: please import a polyfill.");
|
||
|
}
|
||
|
decoder = new TextDecoder("utf-8");
|
||
|
contentType = (_a = response.headers) === null || _a === void 0 ? void 0 : _a.get("content-type");
|
||
|
delimiter = "boundary=";
|
||
|
boundaryVal = (contentType === null || contentType === void 0 ? void 0 : contentType.includes(delimiter)) ?
|
||
|
contentType === null || contentType === void 0 ? void 0 : contentType.substring((contentType === null || contentType === void 0 ? void 0 : contentType.indexOf(delimiter)) + delimiter.length).replace(/['"]/g, "").replace(/\;(.*)/gm, "").trim()
|
||
|
: "-";
|
||
|
boundary = "\r\n--".concat(boundaryVal);
|
||
|
buffer = "";
|
||
|
iterator = responseIterator(response);
|
||
|
running = true;
|
||
|
_e.label = 1;
|
||
|
case 1:
|
||
|
if (!running) return [3 /*break*/, 3];
|
||
|
return [4 /*yield*/, iterator.next()];
|
||
|
case 2:
|
||
|
_b = _e.sent(), value = _b.value, done = _b.done;
|
||
|
chunk = typeof value === "string" ? value : decoder.decode(value);
|
||
|
searchFrom = buffer.length - boundary.length + 1;
|
||
|
running = !done;
|
||
|
buffer += chunk;
|
||
|
bi = buffer.indexOf(boundary, searchFrom);
|
||
|
while (bi > -1) {
|
||
|
message = void 0;
|
||
|
_c = [
|
||
|
buffer.slice(0, bi),
|
||
|
buffer.slice(bi + boundary.length),
|
||
|
], message = _c[0], buffer = _c[1];
|
||
|
i = message.indexOf("\r\n\r\n");
|
||
|
headers = parseHeaders(message.slice(0, i));
|
||
|
contentType_1 = headers["content-type"];
|
||
|
if (contentType_1 &&
|
||
|
contentType_1.toLowerCase().indexOf("application/json") === -1) {
|
||
|
throw new Error("Unsupported patch content type: application/json is required.");
|
||
|
}
|
||
|
body = message.slice(i);
|
||
|
if (body) {
|
||
|
result = parseJsonBody(response, body);
|
||
|
if (Object.keys(result).length > 1 ||
|
||
|
"data" in result ||
|
||
|
"incremental" in result ||
|
||
|
"errors" in result ||
|
||
|
"payload" in result) {
|
||
|
if (isApolloPayloadResult(result)) {
|
||
|
next = {};
|
||
|
if ("payload" in result) {
|
||
|
next = __assign({}, result.payload);
|
||
|
}
|
||
|
if ("errors" in result) {
|
||
|
next = __assign(__assign({}, next), { extensions: __assign(__assign({}, ("extensions" in next ? next.extensions : null)), (_d = {}, _d[PROTOCOL_ERRORS_SYMBOL] = result.errors, _d)) });
|
||
|
}
|
||
|
nextValue(next);
|
||
|
}
|
||
|
else {
|
||
|
// for the last chunk with only `hasNext: false`
|
||
|
// we don't need to call observer.next as there is no data/errors
|
||
|
nextValue(result);
|
||
|
}
|
||
|
}
|
||
|
else if (
|
||
|
// If the chunk contains only a "hasNext: false", we can call
|
||
|
// observer.complete() immediately.
|
||
|
Object.keys(result).length === 1 &&
|
||
|
"hasNext" in result &&
|
||
|
!result.hasNext) {
|
||
|
return [2 /*return*/];
|
||
|
}
|
||
|
}
|
||
|
bi = buffer.indexOf(boundary);
|
||
|
}
|
||
|
return [3 /*break*/, 1];
|
||
|
case 3: return [2 /*return*/];
|
||
|
}
|
||
|
});
|
||
|
});
|
||
|
}
|
||
|
export function parseHeaders(headerText) {
|
||
|
var headersInit = {};
|
||
|
headerText.split("\n").forEach(function (line) {
|
||
|
var i = line.indexOf(":");
|
||
|
if (i > -1) {
|
||
|
// normalize headers to lowercase
|
||
|
var name_1 = line.slice(0, i).trim().toLowerCase();
|
||
|
var value = line.slice(i + 1).trim();
|
||
|
headersInit[name_1] = value;
|
||
|
}
|
||
|
});
|
||
|
return headersInit;
|
||
|
}
|
||
|
export function parseJsonBody(response, bodyText) {
|
||
|
if (response.status >= 300) {
|
||
|
// Network error
|
||
|
var getResult = function () {
|
||
|
try {
|
||
|
return JSON.parse(bodyText);
|
||
|
}
|
||
|
catch (err) {
|
||
|
return bodyText;
|
||
|
}
|
||
|
};
|
||
|
throwServerError(response, getResult(), "Response not successful: Received status code ".concat(response.status));
|
||
|
}
|
||
|
try {
|
||
|
return JSON.parse(bodyText);
|
||
|
}
|
||
|
catch (err) {
|
||
|
var parseError = err;
|
||
|
parseError.name = "ServerParseError";
|
||
|
parseError.response = response;
|
||
|
parseError.statusCode = response.status;
|
||
|
parseError.bodyText = bodyText;
|
||
|
throw parseError;
|
||
|
}
|
||
|
}
|
||
|
export function handleError(err, observer) {
|
||
|
// if it is a network error, BUT there is graphql result info fire
|
||
|
// the next observer before calling error this gives apollo-client
|
||
|
// (and react-apollo) the `graphqlErrors` and `networkErrors` to
|
||
|
// pass to UI this should only happen if we *also* have data as
|
||
|
// part of the response key per the spec
|
||
|
if (err.result && err.result.errors && err.result.data) {
|
||
|
// if we don't call next, the UI can only show networkError
|
||
|
// because AC didn't get any graphqlErrors this is graphql
|
||
|
// execution result info (i.e errors and possibly data) this is
|
||
|
// because there is no formal spec how errors should translate to
|
||
|
// http status codes. So an auth error (401) could have both data
|
||
|
// from a public field, errors from a private field, and a status
|
||
|
// of 401
|
||
|
// {
|
||
|
// user { // this will have errors
|
||
|
// firstName
|
||
|
// }
|
||
|
// products { // this is public so will have data
|
||
|
// cost
|
||
|
// }
|
||
|
// }
|
||
|
//
|
||
|
// the result of above *could* look like this:
|
||
|
// {
|
||
|
// data: { products: [{ cost: "$10" }] },
|
||
|
// errors: [{
|
||
|
// message: 'your session has timed out',
|
||
|
// path: []
|
||
|
// }]
|
||
|
// }
|
||
|
// status code of above would be a 401
|
||
|
// in the UI you want to show data where you can, errors as data where you can
|
||
|
// and use correct http status codes
|
||
|
observer.next(err.result);
|
||
|
}
|
||
|
observer.error(err);
|
||
|
}
|
||
|
export function parseAndCheckHttpResponse(operations) {
|
||
|
return function (response) {
|
||
|
return response
|
||
|
.text()
|
||
|
.then(function (bodyText) { return parseJsonBody(response, bodyText); })
|
||
|
.then(function (result) {
|
||
|
if (!Array.isArray(result) &&
|
||
|
!hasOwnProperty.call(result, "data") &&
|
||
|
!hasOwnProperty.call(result, "errors")) {
|
||
|
// Data error
|
||
|
throwServerError(response, result, "Server response was missing for query '".concat(Array.isArray(operations) ?
|
||
|
operations.map(function (op) { return op.operationName; })
|
||
|
: operations.operationName, "'."));
|
||
|
}
|
||
|
return result;
|
||
|
});
|
||
|
};
|
||
|
}
|
||
|
//# sourceMappingURL=parseAndCheckHttpResponse.js.map
|