@graphql-tools/executor-legacy-ws
Version:
A set of utils for faster development of GraphQL tools
194 lines (193 loc) • 7.69 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.LEGACY_WS = void 0;
exports.buildWSLegacyExecutor = buildWSLegacyExecutor;
const tslib_1 = require("tslib");
const graphql_1 = require("graphql");
const isomorphic_ws_1 = tslib_1.__importDefault(require("isomorphic-ws"));
const utils_1 = require("@graphql-tools/utils");
var LEGACY_WS;
(function (LEGACY_WS) {
LEGACY_WS["CONNECTION_INIT"] = "connection_init";
LEGACY_WS["CONNECTION_ACK"] = "connection_ack";
LEGACY_WS["CONNECTION_ERROR"] = "connection_error";
LEGACY_WS["CONNECTION_KEEP_ALIVE"] = "ka";
LEGACY_WS["START"] = "start";
LEGACY_WS["STOP"] = "stop";
LEGACY_WS["CONNECTION_TERMINATE"] = "connection_terminate";
LEGACY_WS["DATA"] = "data";
LEGACY_WS["ERROR"] = "error";
LEGACY_WS["COMPLETE"] = "complete";
})(LEGACY_WS || (exports.LEGACY_WS = LEGACY_WS = {}));
function isConnectionParamsRecord(value) {
return value != null && typeof value === 'object' && !Array.isArray(value);
}
function buildWSLegacyExecutor(subscriptionsEndpoint, WebSocketImpl, options) {
const disposers = new Set();
let operationSeq = 0;
function baseConnectionParams() {
const connectionParams = options?.connectionParams;
if (typeof connectionParams === 'function') {
const resolved = connectionParams();
return isConnectionParamsRecord(resolved) ? { ...resolved } : {};
}
if (isConnectionParamsRecord(connectionParams)) {
return { ...connectionParams };
}
return {};
}
function requestConnectionParams(request) {
const fromExtensions = request.extensions?.['connectionParams'];
return isConnectionParamsRecord(fromExtensions) ? { ...fromExtensions } : {};
}
const executor = function legacyExecutor(request) {
// Bound to this execution only. A later request must not inherit keys it omits.
const connectionParams = {
...baseConnectionParams(),
...requestConnectionParams(request),
};
operationSeq += 1;
const id = `${operationSeq.toString(36)}-${Math.random().toString(36).slice(2)}`;
return (0, utils_1.observableToAsyncIterable)({
subscribe(observer) {
let closed = false;
const websocket = new WebSocketImpl(subscriptionsEndpoint, 'graphql-ws', {
followRedirects: true,
headers: options?.headers,
rejectUnauthorized: options?.rejectUnauthorized ?? true,
skipUTF8Validation: true,
});
function closeSocket(sendStop) {
if (closed) {
return;
}
closed = true;
disposers.delete(disposeSocket);
if (websocket.readyState === isomorphic_ws_1.default.OPEN) {
if (sendStop) {
websocket.send(JSON.stringify({
type: LEGACY_WS.STOP,
id,
}));
}
websocket.send(JSON.stringify({
type: LEGACY_WS.CONNECTION_TERMINATE,
}));
}
websocket.terminate();
}
function endWithError(err) {
if (closed) {
return;
}
closeSocket(false);
observer.error(err);
observer.complete();
}
function disposeSocket() {
if (closed) {
return;
}
closeSocket(false);
observer.complete();
}
disposers.add(disposeSocket);
websocket.onopen = () => {
if (closed) {
return;
}
websocket.send(JSON.stringify({
type: LEGACY_WS.CONNECTION_INIT,
payload: connectionParams,
}), (error) => {
if (error) {
endWithError(error);
}
});
};
websocket.onerror = event => {
endWithError(event.error);
};
websocket.onclose = () => {
if (closed) {
return;
}
closed = true;
disposers.delete(disposeSocket);
observer.complete();
};
websocket.onmessage = event => {
if (closed) {
return;
}
let data;
try {
data = JSON.parse(event.data.toString('utf-8'));
}
catch (error) {
endWithError(error);
return;
}
switch (data.type) {
case LEGACY_WS.CONNECTION_ACK: {
websocket.send(JSON.stringify({
type: LEGACY_WS.START,
id,
payload: {
query: (0, graphql_1.print)(request.document),
variables: request.variables,
operationName: request.operationName,
},
}), (error) => {
if (error) {
endWithError(error);
}
});
break;
}
case LEGACY_WS.CONNECTION_ERROR: {
endWithError(data.payload);
break;
}
case LEGACY_WS.CONNECTION_KEEP_ALIVE: {
break;
}
case LEGACY_WS.DATA: {
if (data.id !== id) {
break;
}
observer.next(data.payload);
break;
}
case LEGACY_WS.ERROR: {
if (data.id !== id) {
break;
}
endWithError(data.payload);
break;
}
case LEGACY_WS.COMPLETE: {
if (data.id !== id) {
break;
}
closeSocket(false);
observer.complete();
break;
}
}
};
return {
unsubscribe: () => {
closeSocket(true);
},
};
},
});
};
executor[Symbol.dispose] = () => {
for (const disposeSocket of [...disposers]) {
disposeSocket();
}
};
return executor;
}