UNPKG

6.68 kBJavaScriptView Raw
1/**
2 * Kettle Request for use with WebSockets
3 *
4 * Copyright 2012-2013 OCAD University
5 * Copyright 2015 Raising the Floor (International)
6 *
7 * Licensed under the New BSD license. You may not use this file except in
8 * compliance with this License.
9 *
10 * You may obtain a copy of the License at
11 * https://github.com/fluid-project/kettle/blob/master/LICENSE.txt
12 */
13
14"use strict";
15
16var fluid = require("infusion"),
17 kettle = fluid.registerNamespace("kettle");
18
19/*
20 * Refinement of the `kettle.request` grade to handle WebSockets connections. Note that unlike `kettle.request.http`
21 * components, these requests are long-lived and will process numerous messages whilst the WebSockets bus remains
22 * connected. They participate in the Kettle middleware chain only during the process of initial negotiation up
23 * from the base HTTP connection.
24 */
25fluid.defaults("kettle.request.ws", {
26 gradeNames: ["kettle.request"],
27 members: {
28// ws: null // arrives on the `connection` message handled by the kettle.server.ws
29 },
30 events: {
31 onBindWs: null,
32 onReceiveMessage: null,
33 onSendMessage: null
34 },
35 receiveMessageJSON: true, // deserialize all received data as JSON
36 sendMessageJSON: true, // serialize all sent data as JSON
37 listeners: {
38 "onBindWs.ensureResponseDisposes": {
39 funcName: "kettle.request.ws.ensureResponseDisposes",
40 priority: "before:handleRequest"
41 },
42 "onBindWs.listen": {
43 funcName: "kettle.request.ws.listen",
44 priority: "after:ensureResponseDisposes"
45 },
46 "onSendMessage.encode": {
47 funcName: "kettle.request.ws.sendEncode",
48 args: ["{that}.options.sendMessageJSON", "{arguments}.0"],
49 priority: "before:send"
50 },
51 "onSendMessage.send": {
52 funcName: "kettle.request.ws.sendMessageImpl",
53 args: ["{that}", "{arguments}.0"]
54 }
55 },
56 invokers: {
57 sendMessage: {
58 funcName: "kettle.request.ws.sendMessage",
59 args: ["{that}", "{arguments}.0"] // message
60 },
61 sendTypedMessage: {
62 funcName: "kettle.request.ws.sendTypedMessage",
63 args: ["{that}", "{arguments}.0", "{arguments}.1"] // type, payload
64 },
65 handleRequest: "{request}.handlerPromise.resolve()", // by default we simply proceed
66 handleFullRequest: "kettle.request.ws.handleFullRequest"
67 }
68});
69
70fluid.defaults("kettle.request.ws.mismatch", {
71 gradeNames: ["kettle.request.ws", "kettle.request.mismatch"]
72});
73
74// This is handed the verifyClient callback from ws
75kettle.request.ws.handleFullRequest = function (request, fullRequestPromise, verifyCallback) {
76 fullRequestPromise.then(function () {
77 request.events.onRequestSuccess.fire();
78 verifyCallback(true);
79 }, function (err) {
80 // note that these onRequestXxxx events by default have no listeners on a ws request
81 request.events.onRequestError.fire(err);
82 // note that this message cannot be read by the standard client, but we send it anyway. The status code can be read ok
83 verifyCallback(false, err.statusCode, err.message);
84 });
85};
86
87/** Begins listening for firings of the underlying ws `message` event and converts them into firings of the request
88 * component's `onReceiveMessage` event with appropriate request marking.
89 * @param {kettle.request.ws} that - The request component for which should start listening
90 */
91kettle.request.ws.listen = function (that) {
92 that.ws.on("message", function (message) {
93 kettle.withRequest(that, function () {
94 message = that.options.receiveMessageJSON ? kettle.JSON.parse(message) : message;
95 that.events.onReceiveMessage.fire(that, message);
96 })();
97 });
98};
99
100/**
101 * Ensure that the request object is cleared on socket disconnect/close.
102 * @param {kettle.request.ws} that - The request component for which we will listen for socket closure and convert this
103 * into a firing of the `onRequestEnd` event followed by destruction of the component, if this has not already occurred
104 */
105kettle.request.ws.ensureResponseDisposes = function (that) {
106 that.ws.on("close", kettle.withRequest(that, function () {
107 if (!fluid.isDestroyed(that)) {
108 that.events.onRequestEnd.fire();
109 that.destroy();
110 }
111 }));
112};
113
114/** Utility function to encode a conditionally encode a payload as JSON depending on a flag value
115 * @param {Booleanish} encode - If truthy, the supplied payloed will be encoded as JSON
116 * @param {Any} data - The payload value
117 * @return {Any} Either the original payload, or the payload encoded as JSON
118 */
119kettle.request.ws.sendEncode = function (encode, data) {
120 return encode ? JSON.stringify(data) : data;
121};
122
123/** Invokes the transform chain pseudoevent for `onSendMessage`, given an initial message value
124 * @param {kettle.request.ws} that - The request component holding the pseudoevent
125 * @param {Any} message - The message to be sent as initial chain argument
126 * @return {Promise} The resolved value of the `onSendMessage` transform chain, indicating whether the
127 * payload was successfuly sent.
128 */
129kettle.request.ws.sendMessage = function (that, message) {
130 var options = {}; // none currently supported
131 var promise = fluid.promise.fireTransformEvent(that.events.onSendMessage, message, options);
132 return promise;
133};
134
135/** The member of the `onSendMessage` transform chain responsible for actually dispatching the appropriately encoded
136 * payload over the WebSockets bus.
137 * @param {kettle.request.ws} that - The request component holding the WebSockets object
138 * @param {Any} message - The message to be sent
139 * @return {Promise} A promise for the disposition of sending the payload
140 */
141kettle.request.ws.sendMessageImpl = function (that, message) {
142 var promise = fluid.promise();
143 that.ws.send(message, function (err) {
144 promise[err ? "reject" : "resolve"](err);
145 });
146 return promise;
147};
148
149/** A very simple utility facilitating the sending of "typed messages" which are payloads framed by a top-level
150 * String member `type` and the payload itself into the member `payload`
151 * @param {kettle.request.ws} that - The request over which the message is to be sent.
152 * @param {String} type - The type to be ascribed to the payload
153 * @param {JSONable} payload - The payload to be sent as the message
154 * @return {Promise} A promise for the disposition of the message send
155 */
156kettle.request.ws.sendTypedMessage = function (that, type, payload) {
157 return that.sendMessage({
158 type: type,
159 payload: payload
160 });
161};