UNPKG

5.13 kBJavaScriptView Raw
1/**
2 * Kettle Test Utilities for WebSockets
3 *
4 * Contains facilities for issuing WebSocketsrequests encoded declaratively as Infusion components
5 * Copyright 2013-2015 Raising the Floor (International)
6 * Copyright 2013 OCAD University
7 *
8 * Licensed under the New BSD license. You may not use this file except in
9 * compliance with this License.
10 *
11 * You may obtain a copy of the License at
12 * https://github.com/gpii/universal/LICENSE.txt
13 */
14
15"use strict";
16
17var fluid = require("infusion"),
18 kettle = fluid.registerNamespace("kettle");
19
20fluid.require("ws", require, "kettle.npm.ws");
21
22fluid.defaults("kettle.test.request.ws", {
23 gradeNames: ["kettle.test.request"],
24 receiveJSON: true, // deserialize all received data as JSON
25 sendJSON: true, // serialize all sent data as JSON
26 // forwarded to "protocols" argument of new ws.WebSocket() - ws 2.x requires this to be [] rather than null
27 webSocketsProtocols: [],
28 invokers: {
29 connect: {
30 funcName: "kettle.test.request.ws.connect",
31 args: ["{that}", "{cookieJar}", "{arguments}.0"]
32 },
33 send: {
34 funcName: "kettle.test.request.ws.send",
35 args: [
36 "{that}",
37 "{arguments}.0", // model
38 "{arguments}.1" // send options: https://github.com/websockets/ws/blob/master/doc/ws.md#websocketsenddata-options-callback
39 ]
40 },
41 disconnect: {
42 funcName: "kettle.test.request.ws.disconnect",
43 args: "{that}.ws"
44 }
45 },
46 events: {
47 onConnect: null, // fired when we receive "open" for initial connection
48 onReceiveMessage: null,
49 onError: null, // fires if connect fails
50 onClose: null // fires when socket is closed
51 },
52 listeners: {
53 onDestroy: "{that}.disconnect"
54 }
55});
56
57// A variety of WebSockets request that retrieve cookies from a "jar" higher in the component tree
58fluid.defaults("kettle.test.request.wsCookie", {
59 gradeNames: ["kettle.test.request.ws"],
60 storeCookies: true
61});
62
63// permitted options taken from https://github.com/websockets/ws/blob/master/doc/ws.md#new-wswebsocketaddress-protocols-options
64kettle.test.request.ws.requestOptions = ["protocol", "agent", "headers", "protocolVersion", "hostname", "port", "path", "termMap"];
65
66kettle.test.request.ws.connect = function (that, cookieJar, directOptions) {
67 if (that.ws) {
68 fluid.fail("You cannot reuse a kettle.test.request.ws object once it has connected - please construct a fresh component for this request");
69 }
70 var requestOptions = kettle.dataSource.URL.prepareRequestOptions(that.options, cookieJar, directOptions, kettle.test.request.ws.requestOptions, that.resolveUrl);
71
72 var url = "ws://" + requestOptions.hostname + ":" + requestOptions.port + requestOptions.path;
73 fluid.log("connecting ws.WebSocket to: " + url + " with request options ", requestOptions);
74
75 that.ws = new kettle.npm.ws(url, that.options.webSocketsProtocols, requestOptions);
76 that.ws.on("open", function () {
77 that.events.onConnect.fire(that);
78 });
79 that.ws.on("unexpected-response", function (req, res) { // mined out of the source code of ws WebSockets.js to get extra detail on native HTTP response
80 that.nativeResponse = { // to enable parallel processing of errors as for kettle.test.request.http
81 statusCode: res.statusCode
82 };
83 that.events.onError.fire(// Fabricate a response body as if from HTTP response which we can't read
84 JSON.stringify({statusCode: res.statusCode,
85 isError: true,
86 message: "Unexpected HTTP response where WebSockets response was expected"}), that, res);
87 });
88 that.ws.on("error", function (err) {
89 fluid.log("kettle.test.request.ws client error", err);
90 that.events.onError.fire(that, err);
91 });
92 that.ws.on("message", function (data) {
93 fluid.log("kettle.test.request.ws client message", data);
94 that.events.onReceiveMessage.fire(that.options.receiveJSON ? kettle.JSON.parse(data) : data, that);
95 });
96 that.ws.on("close", function (code, reason) {
97 fluid.log("kettle.test.request.ws closed, response code: ", code, ", reason: ", reason);
98 that.events.onClose.fire(that, {
99 code: code,
100 reason: reason
101 });
102 });
103};
104
105kettle.test.request.ws.disconnect = function (ws) {
106 if (ws) {
107 ws.terminate();
108 }
109};
110
111kettle.test.request.ws.send = function (that, model, directOptions) {
112 if (!that.ws) {
113 fluid.fail("Error in kettle.test.request.ws.send - you must first call connect() on this request object before calling send()");
114 }
115 var togo = fluid.promise();
116 var text = that.options.sendJSON ? JSON.stringify(model) : model;
117 that.ws.send(text, directOptions, function (err) {
118 if (err) {
119 that.events.onError.fire(that, err); // TODO: somehow advertise to users that they should not double-handle
120 togo.reject(err);
121 } else {
122 togo.resolve();
123 }
124 });
125 return togo;
126};