server.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.SubscriptionServer = void 0;
  4. var WebSocket = require("ws");
  5. var message_types_1 = require("./message-types");
  6. var protocol_1 = require("./protocol");
  7. var is_object_1 = require("./utils/is-object");
  8. var graphql_1 = require("graphql");
  9. var empty_iterable_1 = require("./utils/empty-iterable");
  10. var iterall_1 = require("iterall");
  11. var is_subscriptions_1 = require("./utils/is-subscriptions");
  12. var parse_legacy_protocol_1 = require("./legacy/parse-legacy-protocol");
  13. var isWebSocketServer = function (socket) { return socket.on; };
  14. var SubscriptionServer = (function () {
  15. function SubscriptionServer(options, socketOptionsOrServer) {
  16. var _this = this;
  17. var onOperation = options.onOperation, onOperationComplete = options.onOperationComplete, onConnect = options.onConnect, onDisconnect = options.onDisconnect, keepAlive = options.keepAlive;
  18. this.specifiedRules = options.validationRules || graphql_1.specifiedRules;
  19. this.loadExecutor(options);
  20. this.onOperation = onOperation;
  21. this.onOperationComplete = onOperationComplete;
  22. this.onConnect = onConnect;
  23. this.onDisconnect = onDisconnect;
  24. this.keepAlive = keepAlive;
  25. if (isWebSocketServer(socketOptionsOrServer)) {
  26. this.wsServer = socketOptionsOrServer;
  27. }
  28. else {
  29. this.wsServer = new WebSocket.Server(socketOptionsOrServer || {});
  30. }
  31. var connectionHandler = (function (socket, request) {
  32. socket.upgradeReq = request;
  33. if (socket.protocol === undefined ||
  34. (socket.protocol.indexOf(protocol_1.GRAPHQL_WS) === -1 && socket.protocol.indexOf(protocol_1.GRAPHQL_SUBSCRIPTIONS) === -1)) {
  35. socket.close(1002);
  36. return;
  37. }
  38. var connectionContext = Object.create(null);
  39. connectionContext.initPromise = Promise.resolve(true);
  40. connectionContext.isLegacy = false;
  41. connectionContext.socket = socket;
  42. connectionContext.request = request;
  43. connectionContext.operations = {};
  44. var connectionClosedHandler = function (error) {
  45. if (error) {
  46. _this.sendError(connectionContext, '', { message: error.message ? error.message : error }, message_types_1.default.GQL_CONNECTION_ERROR);
  47. setTimeout(function () {
  48. connectionContext.socket.close(1011);
  49. }, 10);
  50. }
  51. _this.onClose(connectionContext);
  52. if (_this.onDisconnect) {
  53. _this.onDisconnect(socket, connectionContext);
  54. }
  55. };
  56. socket.on('error', connectionClosedHandler);
  57. socket.on('close', connectionClosedHandler);
  58. socket.on('message', _this.onMessage(connectionContext));
  59. });
  60. this.wsServer.on('connection', connectionHandler);
  61. this.closeHandler = function () {
  62. _this.wsServer.removeListener('connection', connectionHandler);
  63. _this.wsServer.close();
  64. };
  65. }
  66. SubscriptionServer.create = function (options, socketOptionsOrServer) {
  67. return new SubscriptionServer(options, socketOptionsOrServer);
  68. };
  69. Object.defineProperty(SubscriptionServer.prototype, "server", {
  70. get: function () {
  71. return this.wsServer;
  72. },
  73. enumerable: false,
  74. configurable: true
  75. });
  76. SubscriptionServer.prototype.close = function () {
  77. this.closeHandler();
  78. };
  79. SubscriptionServer.prototype.loadExecutor = function (options) {
  80. var execute = options.execute, subscribe = options.subscribe, schema = options.schema, rootValue = options.rootValue;
  81. if (!execute) {
  82. throw new Error('Must provide `execute` for websocket server constructor.');
  83. }
  84. this.schema = schema;
  85. this.rootValue = rootValue;
  86. this.execute = execute;
  87. this.subscribe = subscribe;
  88. };
  89. SubscriptionServer.prototype.unsubscribe = function (connectionContext, opId) {
  90. if (connectionContext.operations && connectionContext.operations[opId]) {
  91. if (connectionContext.operations[opId].return) {
  92. connectionContext.operations[opId].return();
  93. }
  94. delete connectionContext.operations[opId];
  95. if (this.onOperationComplete) {
  96. this.onOperationComplete(connectionContext.socket, opId);
  97. }
  98. }
  99. };
  100. SubscriptionServer.prototype.onClose = function (connectionContext) {
  101. var _this = this;
  102. Object.keys(connectionContext.operations).forEach(function (opId) {
  103. _this.unsubscribe(connectionContext, opId);
  104. });
  105. };
  106. SubscriptionServer.prototype.onMessage = function (connectionContext) {
  107. var _this = this;
  108. return function (message) {
  109. var parsedMessage;
  110. try {
  111. parsedMessage = (0, parse_legacy_protocol_1.parseLegacyProtocolMessage)(connectionContext, JSON.parse(message));
  112. }
  113. catch (e) {
  114. _this.sendError(connectionContext, null, { message: e.message }, message_types_1.default.GQL_CONNECTION_ERROR);
  115. return;
  116. }
  117. var opId = parsedMessage.id;
  118. switch (parsedMessage.type) {
  119. case message_types_1.default.GQL_CONNECTION_INIT:
  120. if (_this.onConnect) {
  121. connectionContext.initPromise = new Promise(function (resolve, reject) {
  122. try {
  123. resolve(_this.onConnect(parsedMessage.payload, connectionContext.socket, connectionContext));
  124. }
  125. catch (e) {
  126. reject(e);
  127. }
  128. });
  129. }
  130. connectionContext.initPromise.then(function (result) {
  131. if (result === false) {
  132. throw new Error('Prohibited connection!');
  133. }
  134. _this.sendMessage(connectionContext, undefined, message_types_1.default.GQL_CONNECTION_ACK, undefined);
  135. if (_this.keepAlive) {
  136. _this.sendKeepAlive(connectionContext);
  137. var keepAliveTimer_1 = setInterval(function () {
  138. if (connectionContext.socket.readyState === WebSocket.OPEN) {
  139. _this.sendKeepAlive(connectionContext);
  140. }
  141. else {
  142. clearInterval(keepAliveTimer_1);
  143. }
  144. }, _this.keepAlive);
  145. }
  146. }).catch(function (error) {
  147. _this.sendError(connectionContext, opId, { message: error.message }, message_types_1.default.GQL_CONNECTION_ERROR);
  148. setTimeout(function () {
  149. connectionContext.socket.close(1011);
  150. }, 10);
  151. });
  152. break;
  153. case message_types_1.default.GQL_CONNECTION_TERMINATE:
  154. connectionContext.socket.close();
  155. break;
  156. case message_types_1.default.GQL_START:
  157. connectionContext.initPromise.then(function (initResult) {
  158. if (connectionContext.operations && connectionContext.operations[opId]) {
  159. _this.unsubscribe(connectionContext, opId);
  160. }
  161. var baseParams = {
  162. query: parsedMessage.payload.query,
  163. variables: parsedMessage.payload.variables,
  164. operationName: parsedMessage.payload.operationName,
  165. context: (0, is_object_1.default)(initResult) ? Object.assign(Object.create(Object.getPrototypeOf(initResult)), initResult) : {},
  166. formatResponse: undefined,
  167. formatError: undefined,
  168. callback: undefined,
  169. schema: _this.schema,
  170. };
  171. var promisedParams = Promise.resolve(baseParams);
  172. connectionContext.operations[opId] = (0, empty_iterable_1.createEmptyIterable)();
  173. if (_this.onOperation) {
  174. var messageForCallback = parsedMessage;
  175. promisedParams = Promise.resolve(_this.onOperation(messageForCallback, baseParams, connectionContext.socket));
  176. }
  177. return promisedParams.then(function (params) {
  178. if (typeof params !== 'object') {
  179. var error = "Invalid params returned from onOperation! return values must be an object!";
  180. _this.sendError(connectionContext, opId, { message: error });
  181. throw new Error(error);
  182. }
  183. if (!params.schema) {
  184. var error = 'Missing schema information. The GraphQL schema should be provided either statically in' +
  185. ' the `SubscriptionServer` constructor or as a property on the object returned from onOperation!';
  186. _this.sendError(connectionContext, opId, { message: error });
  187. throw new Error(error);
  188. }
  189. var document = typeof baseParams.query !== 'string' ? baseParams.query : (0, graphql_1.parse)(baseParams.query);
  190. var executionPromise;
  191. var validationErrors = (0, graphql_1.validate)(params.schema, document, _this.specifiedRules);
  192. if (validationErrors.length > 0) {
  193. executionPromise = Promise.resolve({ errors: validationErrors });
  194. }
  195. else {
  196. var executor = _this.execute;
  197. if (_this.subscribe && (0, is_subscriptions_1.isASubscriptionOperation)(document, params.operationName)) {
  198. executor = _this.subscribe;
  199. }
  200. executionPromise = Promise.resolve(executor({
  201. schema: params.schema,
  202. document: document,
  203. rootValue: _this.rootValue,
  204. contextValue: params.context,
  205. variableValues: params.variables,
  206. operationName: params.operationName,
  207. }));
  208. }
  209. return executionPromise.then(function (executionResult) { return ({
  210. executionIterable: (0, iterall_1.isAsyncIterable)(executionResult) ?
  211. executionResult : (0, iterall_1.createAsyncIterator)([executionResult]),
  212. params: params,
  213. }); });
  214. }).then(function (_a) {
  215. var executionIterable = _a.executionIterable, params = _a.params;
  216. (0, iterall_1.forAwaitEach)(executionIterable, function (value) {
  217. var result = value;
  218. if (params.formatResponse) {
  219. try {
  220. result = params.formatResponse(value, params);
  221. }
  222. catch (err) {
  223. console.error('Error in formatResponse function:', err);
  224. }
  225. }
  226. _this.sendMessage(connectionContext, opId, message_types_1.default.GQL_DATA, result);
  227. })
  228. .then(function () {
  229. _this.sendMessage(connectionContext, opId, message_types_1.default.GQL_COMPLETE, null);
  230. })
  231. .catch(function (e) {
  232. var error = e;
  233. if (params.formatError) {
  234. try {
  235. error = params.formatError(e, params);
  236. }
  237. catch (err) {
  238. console.error('Error in formatError function: ', err);
  239. }
  240. }
  241. if (Object.keys(error).length === 0) {
  242. error = { name: error.name, message: error.message };
  243. }
  244. _this.sendError(connectionContext, opId, error);
  245. });
  246. return executionIterable;
  247. }).then(function (subscription) {
  248. connectionContext.operations[opId] = subscription;
  249. }).then(function () {
  250. _this.sendMessage(connectionContext, opId, message_types_1.default.SUBSCRIPTION_SUCCESS, undefined);
  251. }).catch(function (e) {
  252. if (e.errors) {
  253. _this.sendMessage(connectionContext, opId, message_types_1.default.GQL_DATA, { errors: e.errors });
  254. }
  255. else {
  256. _this.sendError(connectionContext, opId, { message: e.message });
  257. }
  258. _this.unsubscribe(connectionContext, opId);
  259. return;
  260. });
  261. }).catch(function (error) {
  262. _this.sendError(connectionContext, opId, { message: error.message });
  263. _this.unsubscribe(connectionContext, opId);
  264. });
  265. break;
  266. case message_types_1.default.GQL_STOP:
  267. _this.unsubscribe(connectionContext, opId);
  268. break;
  269. default:
  270. _this.sendError(connectionContext, opId, { message: 'Invalid message type!' });
  271. }
  272. };
  273. };
  274. SubscriptionServer.prototype.sendKeepAlive = function (connectionContext) {
  275. if (connectionContext.isLegacy) {
  276. this.sendMessage(connectionContext, undefined, message_types_1.default.KEEP_ALIVE, undefined);
  277. }
  278. else {
  279. this.sendMessage(connectionContext, undefined, message_types_1.default.GQL_CONNECTION_KEEP_ALIVE, undefined);
  280. }
  281. };
  282. SubscriptionServer.prototype.sendMessage = function (connectionContext, opId, type, payload) {
  283. var parsedMessage = (0, parse_legacy_protocol_1.parseLegacyProtocolMessage)(connectionContext, {
  284. type: type,
  285. id: opId,
  286. payload: payload,
  287. });
  288. if (parsedMessage && connectionContext.socket.readyState === WebSocket.OPEN) {
  289. connectionContext.socket.send(JSON.stringify(parsedMessage));
  290. }
  291. };
  292. SubscriptionServer.prototype.sendError = function (connectionContext, opId, errorPayload, overrideDefaultErrorType) {
  293. var sanitizedOverrideDefaultErrorType = overrideDefaultErrorType || message_types_1.default.GQL_ERROR;
  294. if ([
  295. message_types_1.default.GQL_CONNECTION_ERROR,
  296. message_types_1.default.GQL_ERROR,
  297. ].indexOf(sanitizedOverrideDefaultErrorType) === -1) {
  298. throw new Error('overrideDefaultErrorType should be one of the allowed error messages' +
  299. ' GQL_CONNECTION_ERROR or GQL_ERROR');
  300. }
  301. this.sendMessage(connectionContext, opId, sanitizedOverrideDefaultErrorType, errorPayload);
  302. };
  303. return SubscriptionServer;
  304. }());
  305. exports.SubscriptionServer = SubscriptionServer;
  306. //# sourceMappingURL=server.js.map