diff --git a/coap/coap-request.html b/coap/coap-request.html
index f4650d7..2ad656b 100644
--- a/coap/coap-request.html
+++ b/coap/coap-request.html
@@ -7,7 +7,7 @@
value: "GET",
validate: function (v) {
return (
- ["", "GET", "PUT", "POST", "DELETE"].indexOf(v) !== -1
+ ["use", "GET", "PUT", "POST", "DELETE"].indexOf(v) !== -1
);
},
},
@@ -16,8 +16,8 @@
multicastTimeout: { value: 20000, required: false },
url: { value: "" },
"content-format": { value: "text/plain" },
- "raw-buffer": { value: false, required: true },
name: { value: "" },
+ paytoqs: {value: "ignore"},
},
inputs: 1,
outputs: 1,
@@ -29,10 +29,48 @@
labelStyle: function () {
return this.name ? "node_label_italic" : "";
},
+ oneditprepare: function () {
+ function updateMulticastOptions() {
+ if ($("#node-input-multicast").is(":checked")) {
+ $("#node-input-multicast-row").show();
+ } else {
+ $("#node-input-multicast-row").hide();
+ }
+ }
+ if (this.multicast) {
+ $("#node-input-multicast").prop("checked", true);
+ } else {
+ $("#node-input-multicast").prop("checked", false);
+ }
+ updateMulticastOptions();
+ $("#node-input-multicast").on("click", function() {
+ updateMulticastOptions();
+ });
+ $("#node-input-method").on("change", function() {
+ if ($(this).val() === "GET") {
+ $(".node-input-paytoqs-row").show();
+ } else {
+ $(".node-input-paytoqs-row").hide();
+ }
+ });
+ if (this.paytoqs === true || this.paytoqs === "query") {
+ $("#node-input-paytoqs").val("query");
+ } else {
+ $("#node-input-paytoqs").val("ignore");
+ }
+ $("#node-input-method").on("change", function() {
+ if ($(this).val() === "GET") {
+ $(".form-row-coap-request-observe").show();
+ } else {
+ $(".form-row-coap-request-observe").hide();
+ }
+ }).change();
+ }
});
diff --git a/coap/coap-request.js b/coap/coap-request.js
index f4e6d0e..1f8dd3d 100644
--- a/coap/coap-request.js
+++ b/coap/coap-request.js
@@ -9,11 +9,40 @@ module.exports = function (RED) {
RED.nodes.createNode(this, config);
var node = this;
+ var paytoqs = config.paytoqs || "ignore";
+
+ function _stringifyParams(params) {
+ var paramList = [];
+ for (var [key, value] of Object.entries(params)) {
+ var dataType = typeof value;
+ if (["string", "number"].includes(dataType)) {
+ paramList.push(`${key}=${value}`);
+ }
+ }
+
+ return paramList.join("&");
+ }
+
+ function _appendQueryParams(reqOpts, payload) {
+ if (typeof payload === "object") {
+ var newParams = _stringifyParams(payload);
+ if (newParams && reqOpts.query !== "") {
+ newParams = "&" + newParams;
+ }
+
+ reqOpts.query = reqOpts.query + newParams;
+ } else {
+ throw new Error("Hallo");
+ }
+ }
+
function _constructPayload(msg, contentFormat) {
var payload = null;
- if (contentFormat === "text/plain") {
- payload = msg.payload;
+ if (!msg.payload) {
+ return null;
+ } else if (contentFormat === "text/plain") {
+ payload = msg.payload.toString();
} else if (contentFormat === "application/json") {
payload = JSON.stringify(msg.payload);
} else if (contentFormat === "application/cbor") {
@@ -32,18 +61,26 @@ module.exports = function (RED) {
port: url.port,
query: url.search.substring(1),
};
- reqOpts.method = (
- config.method ||
- msg.method ||
- "GET"
- ).toUpperCase();
- reqOpts.headers = {};
- reqOpts.headers["Content-Format"] = config["content-format"];
+ reqOpts.method = config.method.toUpperCase() || "GET";
+ if (config.method === "use" && msg.method != null) {
+ reqOpts.method = msg.method.toUpperCase();
+ }
+
+ reqOpts.headers = msg.headers;
+ if (reqOpts.headers == null) {
+ reqOpts.headers = {};
+ }
+ if (reqOpts.headers["Content-Format"] == null) {
+ reqOpts.headers["Content-Format"] = "application/json";
+ }
reqOpts.multicast = config.multicast;
reqOpts.multicastTimeout = config.multicastTimeout;
function _onResponse(res) {
function _send(payload) {
+ if (!reqOpts.observe) {
+ node.status({});
+ }
node.send(
Object.assign({}, msg, {
payload: payload,
@@ -54,31 +91,38 @@ module.exports = function (RED) {
}
function _onResponseData(data) {
- if (config["raw-buffer"]) {
+ var contentFormat = res.headers["Content-Format"];
+ var configContentFormat = config["content-format"];
+
+ if (config["raw-buffer"] === true || configContentFormat === "raw-buffer") {
_send(data);
- } else if (res.headers["Content-Format"] === "text/plain") {
+ } else if (contentFormat === "text/plain" || configContentFormat === "text/plain") {
_send(data.toString());
- } else if (
- res.headers["Content-Format"] === "application/json"
- ) {
+ } else if (contentFormat.startsWith("application/") && contentFormat.includes("json")) {
try {
_send(JSON.parse(data.toString()));
} catch (error) {
+ node.status({
+ fill: "red",
+ shape: "ring",
+ text: error.message,
+ });
node.error(error.message);
}
- } else if (
- res.headers["Content-Format"] === "application/cbor"
- ) {
- cbor.decodeAll(data, function (err, data) {
- if (err) {
+ } else if (contentFormat.startsWith("application/") && contentFormat.includes("cbor")) {
+ cbor.decodeAll(data, function (error, data) {
+ if (error) {
+ node.error(error.message);
+ node.status({
+ fill: "red",
+ shape: "ring",
+ text: error.message,
+ });
return false;
}
_send(data[0]);
});
- } else if (
- res.headers["Content-Format"] ===
- "application/link-format"
- ) {
+ } else if (contentFormat === "application/link-format") {
_send(linkFormat.parse(data.toString()));
} else {
_send(data.toString());
@@ -87,12 +131,28 @@ module.exports = function (RED) {
res.on("data", _onResponseData);
- if (reqOpts.observe) {
+ if (reqOpts.observe === true) {
+ node.status({
+ fill: "blue",
+ shape: "dot",
+ text: "coapRequest.status.observing",
+ });
node.stream = res;
}
}
- var payload = _constructPayload(msg, config["content-format"]);
+ var payload;
+
+ if (reqOpts.method !== "GET") {
+ payload = _constructPayload(msg, reqOpts.headers["Content-Format"]);
+ } else if (paytoqs === "query") {
+ try {
+ _appendQueryParams(reqOpts, msg.payload);
+ } catch (error) {
+ node.error("Coap request: Invalid payload format!");
+ return;
+ }
+ }
if (config.observe === true) {
reqOpts.observe = "1";
@@ -100,27 +160,41 @@ module.exports = function (RED) {
delete reqOpts.observe;
}
- //TODO: should revisit this block
+ // TODO: should revisit this block
if (node.stream) {
node.stream.close();
}
var req = coap.request(reqOpts);
req.on("response", _onResponse);
- req.on("error", function (err) {
+ req.on("error", function (error) {
+ node.status({
+ fill: "red",
+ shape: "ring",
+ text: error.message,
+ });
node.log("client error");
- node.log(err);
+ node.log(error.message);
});
- if (payload) {
+ if (payload != null) {
req.write(payload);
}
req.end();
}
this.on("input", function (msg) {
+ node.status({
+ fill: "blue",
+ shape: "dot",
+ text: "coapRequest.status.requesting",
+ });
_makeRequest(msg);
});
+
+ this.on("close", function () {
+ node.status({});
+ });
}
RED.nodes.registerType("coap request", CoapRequestNode);
};
diff --git a/coap/locales/de/coap-request.json b/coap/locales/de/coap-request.json
index f9df1c5..2929920 100644
--- a/coap/locales/de/coap-request.json
+++ b/coap/locales/de/coap-request.json
@@ -16,7 +16,7 @@
"label": "Multicast erzwingen"
},
"inputMulticastTimeout": {
- "label": "Multicast-Timeout (in ms)"
+ "label": "Timeout (in ms)"
},
"inputObserve": {
"label": "Observe?"
@@ -27,6 +27,17 @@
"inputContentFormat": {
"label": "Content-Format"
},
+ "return": {
+ "label": "Rückgabe",
+ "utf8": "Eine UTF-8-Zeichenfolge",
+ "binary": "Einen binären Buffer",
+ "json": "Ein geparstes JSON- oder CBOR-Objekt"
+ },
+ "status": {
+ "observing": "beobachten",
+ "requesting": "anfordern",
+ "no-response": "Keine Antwort vom Server"
+ },
"tip": "Tipp: Lassen Sie den URL oder die Methode leer, wenn Sie diese über msg-Eigenschaften definieren wollen."
}
}
diff --git a/coap/locales/en-US/coap-request.json b/coap/locales/en-US/coap-request.json
index f622dbb..2fcb03e 100644
--- a/coap/locales/en-US/coap-request.json
+++ b/coap/locales/en-US/coap-request.json
@@ -16,7 +16,7 @@
"label": "Force multicast"
},
"inputMulticastTimeout": {
- "label": "Multicast timeout (in ms)"
+ "label": "Timeout (in ms)"
},
"inputObserve": {
"label": "Observe?"
@@ -27,6 +27,26 @@
"inputContentFormat": {
"label": "Content format"
},
+ "return": {
+ "label": "Return",
+ "utf8": "a UTF-8 string",
+ "binary": "a binary buffer",
+ "json": "a parsed JSON or CBOR object"
+ },
+ "setby": {
+ "label": "- set by msg.method -"
+ },
+ "paytoqs": {
+ "label": "Payload",
+ "ignore": "Ignore",
+ "query": "Append to query-string parameters",
+ "body": "Send as request body"
+ },
+ "status": {
+ "observing": "observing",
+ "requesting": "requesting",
+ "no-response": "no response from server"
+ },
"tip": "Tip: Leave url or method blank if you want to set them via msg properties."
}
}
diff --git a/test/coap-request_spec.js b/test/coap-request_spec.js
index 58ba0fb..00cfc55 100644
--- a/test/coap-request_spec.js
+++ b/test/coap-request_spec.js
@@ -141,7 +141,7 @@ describe("CoapRequestNode", function () {
id: "n3",
type: "coap request",
"content-format": "text/plain",
- method: "",
+ method: "use",
name: "coapRequest",
observe: false,
url: "coap://localhost:" + port + "/test-resource",
@@ -619,8 +619,18 @@ describe("CoapRequestNode", function () {
id: "n1",
type: "inject",
name: "Fire once",
- payload: test.message,
- payloadType: "string",
+ props: [
+ {
+ "p": "payload",
+ "vt": "string",
+ "v": test.message
+ },
+ {
+ "p": "headers",
+ "v": {"Content-Format": test.format},
+ "vt": "object"
+ }
+ ],
repeat: "",
crontab: "",
once: true,
@@ -629,7 +639,6 @@ describe("CoapRequestNode", function () {
{
id: "n2",
type: "coap request",
- "content-format": test.format,
method: "POST",
name: "coapRequestPost",
observe: false,