Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Working on issues (forward v3) #465

Merged
merged 22 commits into from
May 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# https://medium.com/@kahana.hagai/docker-compose-with-node-js-and-mongodb-dbdadab5ce0a

# The instructions for the first stage
FROM node:16-alpine as builder
FROM node:20-alpine as builder

ARG NODE_ENV=production
ENV NODE_ENV=${NODE_ENV}
Expand All @@ -22,7 +22,7 @@ RUN npm install


# The instructions for second stage
FROM node:16-alpine
FROM node:20-alpine

WORKDIR /opt/OpenHaus/backend
COPY --from=builder node_modules node_modules
Expand Down
25 changes: 24 additions & 1 deletion components/devices/class.device.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ const Interface = require("./class.interface.js");
const Item = require("../../system/component/class.item.js");

const mixins = require("../../helper/mixins.js");
const injectMethod = require("../../helper/injectMethod.js");

//const { parse, calculateChecksum } = require("./net-helper.js");

/**
* @description
Expand All @@ -25,7 +28,7 @@ const mixins = require("../../helper/mixins.js");
* @see interfaceStream components/devices/class.interfaceStream.js
*/
module.exports = class Device extends Item {
constructor(props) {
constructor(props, scope) {

super(props);

Expand All @@ -38,6 +41,11 @@ module.exports = class Device extends Item {
// for each interface class, create a interface stream
this.interfaces = props.interfaces.map((obj) => {


// NOTE: refactor interfaceStream in v4
// move .bridge method there and pass device instance?
// > Would this also create a ciruclar reference in Interface class
// > since its stored via `Object.defineProperty(this, "stream",...);`
let stream = new InterfaceStream({
// duplex stream options
emitClose: false
Expand All @@ -50,6 +58,21 @@ module.exports = class Device extends Item {
let iface = new Interface(obj, stream);


// inject bridge method into interface instance
// passing deivce instance into Interface class, creates a ciruclar reference
// TODO: Move this into "interfaceStream" (needs to be refactored)
// NOTE: remove "device" for bridging requests (only needed in connector)?
// > See: https://github.com/OpenHausIO/connector/issues/54
// > When done, "device" property can be removed, and the `.bridge()` method can be moved into Interface class
injectMethod(iface, "bridge", (cb) => {
return Interface._bridge({
events: scope.events,
interface: iface,
device: this._id
}, cb);
});


// "hide" stream behind iface object
// so we can use the interface object
// as duplex stream
Expand Down
79 changes: 79 additions & 0 deletions components/devices/class.interface.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@ const Joi = require("joi");
const { Agent } = require("http");
const mongodb = require("mongodb");
const { Transform, Duplex } = require("stream");
const { randomUUID } = require("crypto");
//const path = require("path");

//const Adapter = require("./class.adapter.js");


const timeout = require("../../helper/timeout.js");
const promisfy = require("../../helper/promisify.js");


/**
* @description
Expand Down Expand Up @@ -372,5 +381,75 @@ module.exports = class Interface {
}


// bridge methods connects adapter with the underlaying network socket
// create a `.socket()` method that returns the palin websocket stream
static _bridge({ device, interface: iface, events }, cb) {
return promisfy((done) => {

console.log("Bridge request, iface", iface, device);

// create a random uuid
// used as identifier for responses
let uuid = randomUUID();
//let uuid = "4c6de542-f89f-42ac-a2b5-1c26f9e68d73";


// timeout after certain time
// no connector available, not mocks or whatever reaseon
let caller = timeout(5000, (timedout, duration, args) => {
if (timedout) {
done(new Error("TIMEDOUT"));
} else {
done(null, args[0]);
}
});


// socket response handler
// listen for uuid and compare it with generated
let handler = ({ stream, type, uuid: id, socket }) => {
if (uuid === id && type === "response" && socket) {

console.log("adapter", iface.adapter);

/*
// create adapter stack here
// pass adapter stack as caller argument
//caller(stack);
let stack = iface.adapter.map((name) => {
try {
return require(path.join(process.cwd(), "adapter", `${name}.js`))();
} catch (err) {
console.error(`Error in adapter "${name}" `, err);
}
});

console.log("stack", stack);

stream = new Adapter(stack, stream, {
emitClose: false,
end: false
});

console.log("stream", stream)
*/

caller(stream);

}
};

events.on("socket", handler);

events.emit("socket", {
uuid,
device,
interface: iface._id,
type: "request"
});

}, cb);
}


};
9 changes: 9 additions & 0 deletions components/plugins/class.plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,15 @@ module.exports = class Plugin extends Item {
let init = (dependencies, cb) => {
try {

// NOTE: Monkey patch ready/abort method to init?
// A plugin could siganlize if its ready or needs to be restarted
/*
let init = new Promise((resolve, reject) => {
init.ready = resolve;
init.abort = reject;
});
*/

const granted = dependencies.every((c) => {
if (this.intents.includes(c)) {

Expand Down
35 changes: 35 additions & 0 deletions helper/injectMethod.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* @function injectMethod
* Add a method to a given object into the prototype
* Default values are set to the exact same values when the method would be defined in the object/class body
*
* @param {Object} obj Object to add property to
* @param {String} prop The property name
* @param {*} value Value of the property
* @param {Object} [options={}] Property descriptor options
* @param {Boolean} [options.writable=true]
* @param {Boolean} [options.enumerable=false]
* @param {Boolean} [options.configurable=true]
*
* @link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty#description
*/

function injectMethod(obj, prop, value, options = {}) {

if (!(value instanceof Function)) {
throw new TypeError(`Value must be a function, received ${typeof value}`);
}

// NOTE: Setting on prototype of given object, breaks iface.bridge()...
// Object.defineProperty(Object.getPrototypeOf(obj), prop, {
Object.defineProperty(obj, prop, {
value,
writable: true,
enumerable: false,
configurable: true,
...options
});

}

module.exports = injectMethod;
46 changes: 26 additions & 20 deletions helper/request.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
const url = require("url");

const promisify = require("./promisify.js");

/**
* Does a http request
* @param {*} uri
Expand All @@ -12,6 +14,11 @@ const url = require("url");
*/
function perform(uri, options, cb) {

if(!options && !cb){
options = {};
cb = () => {};
}

let { protocol } = new url.URL(uri);

if (!["http:", "https:"].includes(protocol)) {
Expand Down Expand Up @@ -51,7 +58,9 @@ function perform(uri, options, cb) {
cb(null, {
headers: res.headers,
status: res.statusCode,
body
body,
res,
req: request
});

});
Expand Down Expand Up @@ -84,17 +93,13 @@ function perform(uri, options, cb) {

* @returns {http.ClientRequest} https://nodejs.org/dist/latest-v16.x/docs/api/http.html#class-httpclientrequest
*/
module.exports = function request(uri, options, cb) {
function request(uri, options, cb) {

if (!cb && options instanceof Function) {
cb = options;
options = {};
}

if (!cb) {
cb = () => { };
}

options = Object.assign({
method: "GET",
body: "",
Expand All @@ -103,25 +108,26 @@ module.exports = function request(uri, options, cb) {
setKeepAliveHeader: true
}, options);

return promisify((done) => {
perform(uri, options, (err, result) => {
if (err) {

return perform(uri, options, (err, result) => {
if (err) {

cb(err);

} else {

if (options.followRedirects && result.status >= 300 && result.status < 400) {

perform(result.headers.location, options, cb);
done(err);

} else {

cb(null, result);
if (options.followRedirects && result.status >= 300 && result.status < 400 && result.headers?.location) {
perform(result.headers.location, options, done);
} else {
done(null, result);
}

}
});
}, cb);

}
});
}

};
module.exports = Object.assign(request, {
perform
});
Loading