Compare commits

...
10 Commits
Author SHA1 Message Date
Zaid Humaimidi 0f6604ffbd 1.2.12 2024-03-28 12:56:35 -07:00
Zaid Humaimidi f57a26c95b Changelog for v1.2.12 2024-03-28 12:04:31 -07:00
Zaid Humaimidi 60e6d8b175 Fix regression in CSV and JSON streaming 2024-03-27 14:08:35 -07:00
Zaid Humaimidi ab80a62c5c Fix bug preventing XMPP client from connecting 2024-03-27 13:57:09 -07:00
Zaid Humaimidi 3134b63b68 Add requirements.txt for RTD 2024-03-21 13:06:33 -07:00
Zaid Humaimidi 2b1bca7dde Add .readthedocs.yaml file 2024-03-21 12:27:40 -07:00
Zaid Humaimidi fd046ea8e3 1.2.11 2024-03-21 12:03:24 -07:00
Zaid Humaimidi 8091907071 Changelog for v1.2.11 2024-03-21 11:32:43 -07:00
Zaid Humaimidi 872aa1c608 Update dependencies 2024-03-21 11:29:32 -07:00
Zaid Humaimidi cdbfb172c7 Remove certificate validation check
Breaks compatibility with Node 12 and it's not really necessary anyway.
2024-03-20 17:11:14 -07:00
10 changed files with 163 additions and 150 deletions

No files matched your search

+12
View File
@@ -1,5 +1,17 @@
# Change Log # Change Log
## 1.2.12 (2024-03-28)
- Fix broken XMPP support in the previous release.
- Fix regression causing CSV downloads to be buffered in memory before being
streamed to the client.
## 1.2.11 (2024-03-21)
- Resolved an issue from the previous release that caused incompatibility with
Node.js versions 12 through 15.
## 1.2.10 (2024-03-18) ## 1.2.10 (2024-03-18)
- Add support for XMPP connection requests. Use the environment variables - Add support for XMPP connection requests. Use the environment variables
+1
View File
@@ -176,3 +176,4 @@ NaN
CIDR CIDR
XMPP XMPP
PEM PEM
JID
+18
View File
@@ -0,0 +1,18 @@
# Read the Docs configuration file for Sphinx projects
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
version: 2
build:
os: ubuntu-22.04
tools:
python: "3.12"
sphinx:
configuration: docs/conf.py
formats: all
python:
install:
- requirements: docs/requirements.txt
+2 -2
View File
@@ -19,7 +19,7 @@ import json
# -- Project information ----------------------------------------------------- # -- Project information -----------------------------------------------------
project = 'GenieACS Documentation' project = 'GenieACS Documentation'
copyright = '2019, GenieACS Inc.' copyright = '2024, GenieACS Inc.'
author = 'GenieACS Inc.' author = 'GenieACS Inc.'
# The full version, including alpha/beta/rc tags # The full version, including alpha/beta/rc tags
@@ -58,7 +58,7 @@ html_theme_options = {
# Add any paths that contain custom static files (such as style sheets) here, # Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files, # relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css". # so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static'] # html_static_path = ['_static']
master_doc = 'index' master_doc = 'index'
+1
View File
@@ -0,0 +1 @@
sphinx_rtd_theme==2.0.0
-15
View File
@@ -3,7 +3,6 @@ import * as http from "node:http";
import * as https from "node:https"; import * as https from "node:https";
import { Socket } from "node:net"; import { Socket } from "node:net";
import * as path from "node:path"; import * as path from "node:path";
import { X509Certificate, createPrivateKey } from "node:crypto";
import { ROOT_DIR } from "./config.ts"; import { ROOT_DIR } from "./config.ts";
let server: http.Server | https.Server; let server: http.Server | https.Server;
@@ -67,13 +66,6 @@ function getValidPrivKeys(value: string): Buffer[] {
const buf = str.startsWith("-----BEGIN ") const buf = str.startsWith("-----BEGIN ")
? Buffer.from(str) ? Buffer.from(str)
: readFileSync(path.resolve(ROOT_DIR, str)); : readFileSync(path.resolve(ROOT_DIR, str));
try {
createPrivateKey(buf);
} catch (err) {
throw new Error(`Invalid private key: ${err.message}`);
}
return buf; return buf;
}); });
} }
@@ -84,13 +76,6 @@ function getValidCerts(value: string): Buffer[] {
const buf = str.startsWith("-----BEGIN ") const buf = str.startsWith("-----BEGIN ")
? Buffer.from(str) ? Buffer.from(str)
: readFileSync(path.resolve(ROOT_DIR, str)); : readFileSync(path.resolve(ROOT_DIR, str));
try {
new X509Certificate(buf);
} catch (err) {
throw new Error(`Invalid certificate: ${err.message}`);
}
return buf; return buf;
}); });
} }
+66 -64
View File
@@ -1,4 +1,4 @@
import { PassThrough } from "node:stream"; import { Readable } from "node:stream";
import Router from "koa-router"; import Router from "koa-router";
import { ObjectId } from "mongodb"; import { ObjectId } from "mongodb";
import * as db from "./db.ts"; import * as db from "./db.ts";
@@ -88,10 +88,9 @@ router.get(`/devices/:id.csv`, async (ctx) => {
.replace(/[:.]/g, "")}.csv`, .replace(/[:.]/g, "")}.csv`,
); );
ctx.body = new PassThrough(); const lines: string[] = [
ctx.body.write( "Parameter,Object,Object timestamp,Writable,Writable timestamp,Value,Value type,Value timestamp,Notification,Notification timestamp,Access list,Access list timestamp",
"Parameter,Object,Object timestamp,Writable,Writable timestamp,Value,Value type,Value timestamp,Notification,Notification timestamp,Access list,Access list timestamp\n", ];
);
for (const k of Object.keys(device).sort()) { for (const k of Object.keys(device).sort()) {
const p = device[k]; const p = device[k];
@@ -118,9 +117,9 @@ router.get(`/devices/:id.csv`, async (ctx) => {
p.accessList ? p.accessList.join(", ") : "", p.accessList ? p.accessList.join(", ") : "",
new Date(p.accessListTimestamp).toJSON(), new Date(p.accessListTimestamp).toJSON(),
]; ];
ctx.body.write(row.map((r) => (r != null ? r : "")).join(",") + "\n"); lines.push(row.map((r) => (r != null ? r : "")).join(","));
} }
ctx.body.end(); ctx.body = lines.join("\n");
logger.accessInfo(log); logger.accessInfo(log);
}); });
@@ -201,16 +200,17 @@ for (const [resource, flags] of Object.entries(resources)) {
]); ]);
} }
ctx.body = new PassThrough();
ctx.type = "application/json";
let c = 0;
ctx.body.write("[\n");
for await (const obj of db.query(resource, filter, options))
ctx.body.write((c++ ? "," : "") + JSON.stringify(obj) + "\n");
ctx.body.end("]");
logger.accessInfo(log); logger.accessInfo(log);
ctx.type = "application/json";
ctx.body = Readable.from(
(async function* () {
let c = 0;
yield "[\n";
for await (const obj of db.query(resource, filter, options))
yield (c++ ? "," : "") + JSON.stringify(obj) + "\n";
yield "]";
})(),
);
}); });
// CSV download // CSV download
@@ -260,60 +260,62 @@ for (const [resource, flags] of Object.entries(resources)) {
]); ]);
} }
ctx.body = new PassThrough(); logger.accessInfo(log);
ctx.type = "text/csv"; ctx.type = "text/csv";
ctx.attachment( ctx.attachment(
`${resource}-${new Date(now).toISOString().replace(/[:.]/g, "")}.csv`, `${resource}-${new Date(now).toISOString().replace(/[:.]/g, "")}.csv`,
); );
ctx.body.write( ctx.body = Readable.from(
Object.keys(columns).map((k) => `"${k.replace(/"/, '""')}"`) + "\n", (async function* () {
yield Object.keys(columns).map((k) => `"${k.replace(/"/, '""')}"`) +
"\n";
for await (const obj of db.query(resource, filter, options)) {
const arr = Object.values(columns).map((exp) => {
const v = evaluate(exp, obj, null, (e) => {
if (Array.isArray(e)) {
if (e[0] === "PARAM") {
if (resource === "devices") {
if (e[1] === "Tags") {
const tags = [];
for (const p in obj)
if (p.startsWith("Tags."))
tags.push(decodeTag(p.slice(5)));
return tags.join(", ");
}
if (e === exp) {
const p = obj[e[1]];
if (
p &&
p.value &&
p.value[1] === "xsd:dateTime" &&
typeof p.value[0] === "number"
)
return new Date(p.value[0]).toJSON();
}
} else if (resource === "faults") {
if (e[1] === "detail") return yamlStringify(obj["detail"]);
}
} else if (e[0] === "FUNC") {
if (e[1] === "DATE_STRING") {
if (e[2] && !Array.isArray(e[2]))
return new Date(e[2]).toJSON();
}
}
}
return e;
});
if (Array.isArray(v) || v == null) return "";
if (typeof v === "string") return `"${v.replace(/"/g, '""')}"`;
return v;
});
yield arr.join(",") + "\n";
}
})(),
); );
for await (const obj of db.query(resource, filter, options)) {
const arr = Object.values(columns).map((exp) => {
const v = evaluate(exp, obj, null, (e) => {
if (Array.isArray(e)) {
if (e[0] === "PARAM") {
if (resource === "devices") {
if (e[1] === "Tags") {
const tags = [];
for (const p in obj)
if (p.startsWith("Tags.")) tags.push(decodeTag(p.slice(5)));
return tags.join(", ");
}
if (e === exp) {
const p = obj[e[1]];
if (
p &&
p.value &&
p.value[1] === "xsd:dateTime" &&
typeof p.value[0] === "number"
)
return new Date(p.value[0]).toJSON();
}
} else if (resource === "faults") {
if (e[1] === "detail") return yamlStringify(obj["detail"]);
}
} else if (e[0] === "FUNC") {
if (e[1] === "DATE_STRING") {
if (e[2] && !Array.isArray(e[2]))
return new Date(e[2]).toJSON();
}
}
}
return e;
});
if (Array.isArray(v) || v == null) return "";
if (typeof v === "string") return `"${v.replace(/"/g, '""')}"`;
return v;
});
ctx.body.write(arr.join(",") + "\n");
}
ctx.body.end();
logger.accessInfo(log);
}); });
router.head(`/${resource}/:id`, async (ctx) => { router.head(`/${resource}/:id`, async (ctx) => {
+36 -42
View File
@@ -321,50 +321,44 @@ export default class XmppClient extends EventEmitter {
this._iqStanzaCallbacks = new Map(); this._iqStanzaCallbacks = new Map();
} }
static connect(opts: XmppClientOptions): Promise<XmppClient> { static async connect(opts: XmppClientOptions): Promise<XmppClient> {
return new Promise((resolve, reject) => { function connectSocket(host: string, port: number): Promise<net.Socket> {
let socket = new net.Socket(); return new Promise((resolve, reject) => {
socket.on("error", reject); const socket = new net.Socket();
socket.on("error", reject);
socket.connect( socket.connect(port, host, () => {
opts.port || 5222,
opts.host,
void async function () {
socket.removeListener("error", reject); socket.removeListener("error", reject);
try { resolve(socket);
let status = 1; });
while (status) { });
if (status === STATUS_STARTTLS) }
socket = await upgradeTls(socket, opts.host);
status = await xmppStream(
socket,
init(
socket,
opts.host,
opts.username,
opts.password,
opts.resource,
),
);
}
const client = new XmppClient(); let socket = await connectSocket(opts.host, opts.port || 5222);
client._socket = socket; try {
client._host = opts.host; let status = 1;
client._username = opts.username; while (status) {
client._resource = opts.resource; if (status === STATUS_STARTTLS)
socket.on("data", client._onData.bind(client)); socket = await upgradeTls(socket, opts.host);
socket.on("error", client._onError.bind(client)); status = await xmppStream(
if (opts.timeout) socket,
socket.setTimeout(opts.timeout, client.close.bind(client)); init(socket, opts.host, opts.username, opts.password, opts.resource),
resolve(client); );
} catch (err) { }
socket.destroy(); } catch (err) {
reject(err); socket.destroy();
} throw err;
}, }
);
}); const client = new XmppClient();
client._socket = socket;
client._host = opts.host;
client._username = opts.username;
client._resource = opts.resource;
socket.on("data", client._onData.bind(client));
socket.on("error", client._onError.bind(client));
if (opts.timeout)
socket.setTimeout(opts.timeout, client.close.bind(client));
return client;
} }
close(): void { close(): void {
+23 -23
View File
@@ -1,12 +1,12 @@
{ {
"name": "genieacs", "name": "genieacs",
"version": "1.2.10", "version": "1.2.12",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "genieacs", "name": "genieacs",
"version": "1.2.10", "version": "1.2.12",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"dependencies": { "dependencies": {
"@breejs/later": "^4.2.0", "@breejs/later": "^4.2.0",
@@ -15,7 +15,7 @@
"iconv-lite": "^0.6.3", "iconv-lite": "^0.6.3",
"ipaddr.js": "^2.0.1", "ipaddr.js": "^2.0.1",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"koa": "^2.15.1", "koa": "^2.15.2",
"koa-bodyparser": "^4.3.0", "koa-bodyparser": "^4.3.0",
"koa-compress": "^5.1.0", "koa-compress": "^5.1.0",
"koa-jwt": "^4.0.3", "koa-jwt": "^4.0.3",
@@ -39,7 +39,7 @@
"@types/koa-router": "^7.4.8", "@types/koa-router": "^7.4.8",
"@types/koa-static": "^4.0.4", "@types/koa-static": "^4.0.4",
"@types/mithril": "^2.2.6", "@types/mithril": "^2.2.6",
"@types/node": "^20.11.29", "@types/node": "^20.11.30",
"@types/parsimmon": "^1.10.9", "@types/parsimmon": "^1.10.9",
"@types/seedrandom": "^3.0.8", "@types/seedrandom": "^3.0.8",
"@typescript-eslint/eslint-plugin": "^7.3.1", "@typescript-eslint/eslint-plugin": "^7.3.1",
@@ -52,7 +52,7 @@
"prettier": "^3.2.5", "prettier": "^3.2.5",
"sql.js": "^1.10.2", "sql.js": "^1.10.2",
"svgo": "^3.2.0", "svgo": "^3.2.0",
"typescript": "^5.4.2", "typescript": "^5.4.3",
"yaml": "^1.10.2" "yaml": "^1.10.2"
}, },
"engines": { "engines": {
@@ -2064,9 +2064,9 @@
"dev": true "dev": true
}, },
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "20.11.29", "version": "20.11.30",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.29.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.30.tgz",
"integrity": "sha512-P99thMkD/1YkCvAtOd6/zGedKNA0p2fj4ZpjCzcNiSCBWgm3cNRTBfa/qjFnsKkkojxu4vVLtWpesnZ9+ap+gA==", "integrity": "sha512-dHM6ZxwlmuZaRmUPfv1p+KrdD1Dci04FbdEm/9wEMouFqxYoFl5aMkt0VMAUtYRQDyYvD41WJLukhq/ha3YuTw==",
"dependencies": { "dependencies": {
"undici-types": "~5.26.4" "undici-types": "~5.26.4"
} }
@@ -3890,9 +3890,9 @@
} }
}, },
"node_modules/koa": { "node_modules/koa": {
"version": "2.15.1", "version": "2.15.2",
"resolved": "https://registry.npmjs.org/koa/-/koa-2.15.1.tgz", "resolved": "https://registry.npmjs.org/koa/-/koa-2.15.2.tgz",
"integrity": "sha512-kpxzGxsv7tlc0WmccWd6CfdWqYXk4o/FsCTjnKaDnHLjPK/Sy1MpoBkuKO5LN7GdPHgPljrAVmMO3wbFxEJTeA==", "integrity": "sha512-MXTeZH3M6AJ8ukW2QZ8wqO3Dcdfh2WRRmjCBkEP+NhKNCiqlO5RDqHmSnsyNrbRJrdjyvIGSJho4vQiWgQJSVA==",
"dependencies": { "dependencies": {
"accepts": "^1.3.5", "accepts": "^1.3.5",
"cache-content-type": "^1.0.0", "cache-content-type": "^1.0.0",
@@ -5061,9 +5061,9 @@
} }
}, },
"node_modules/typescript": { "node_modules/typescript": {
"version": "5.4.2", "version": "5.4.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.2.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz",
"integrity": "sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==", "integrity": "sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==",
"dev": true, "dev": true,
"bin": { "bin": {
"tsc": "bin/tsc", "tsc": "bin/tsc",
@@ -6760,9 +6760,9 @@
"dev": true "dev": true
}, },
"@types/node": { "@types/node": {
"version": "20.11.29", "version": "20.11.30",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.29.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.30.tgz",
"integrity": "sha512-P99thMkD/1YkCvAtOd6/zGedKNA0p2fj4ZpjCzcNiSCBWgm3cNRTBfa/qjFnsKkkojxu4vVLtWpesnZ9+ap+gA==", "integrity": "sha512-dHM6ZxwlmuZaRmUPfv1p+KrdD1Dci04FbdEm/9wEMouFqxYoFl5aMkt0VMAUtYRQDyYvD41WJLukhq/ha3YuTw==",
"requires": { "requires": {
"undici-types": "~5.26.4" "undici-types": "~5.26.4"
} }
@@ -8076,9 +8076,9 @@
} }
}, },
"koa": { "koa": {
"version": "2.15.1", "version": "2.15.2",
"resolved": "https://registry.npmjs.org/koa/-/koa-2.15.1.tgz", "resolved": "https://registry.npmjs.org/koa/-/koa-2.15.2.tgz",
"integrity": "sha512-kpxzGxsv7tlc0WmccWd6CfdWqYXk4o/FsCTjnKaDnHLjPK/Sy1MpoBkuKO5LN7GdPHgPljrAVmMO3wbFxEJTeA==", "integrity": "sha512-MXTeZH3M6AJ8ukW2QZ8wqO3Dcdfh2WRRmjCBkEP+NhKNCiqlO5RDqHmSnsyNrbRJrdjyvIGSJho4vQiWgQJSVA==",
"requires": { "requires": {
"accepts": "^1.3.5", "accepts": "^1.3.5",
"cache-content-type": "^1.0.0", "cache-content-type": "^1.0.0",
@@ -8931,9 +8931,9 @@
} }
}, },
"typescript": { "typescript": {
"version": "5.4.2", "version": "5.4.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.2.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz",
"integrity": "sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==", "integrity": "sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==",
"dev": true "dev": true
}, },
"undici-types": { "undici-types": {
+4 -4
View File
@@ -1,6 +1,6 @@
{ {
"name": "genieacs", "name": "genieacs",
"version": "1.2.10", "version": "1.2.12",
"description": "A TR-069 Auto Configuration Server (ACS)", "description": "A TR-069 Auto Configuration Server (ACS)",
"repository": { "repository": {
"type": "git", "type": "git",
@@ -31,7 +31,7 @@
"iconv-lite": "^0.6.3", "iconv-lite": "^0.6.3",
"ipaddr.js": "^2.0.1", "ipaddr.js": "^2.0.1",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"koa": "^2.15.1", "koa": "^2.15.2",
"koa-bodyparser": "^4.3.0", "koa-bodyparser": "^4.3.0",
"koa-compress": "^5.1.0", "koa-compress": "^5.1.0",
"koa-jwt": "^4.0.3", "koa-jwt": "^4.0.3",
@@ -49,7 +49,7 @@
"@types/koa-router": "^7.4.8", "@types/koa-router": "^7.4.8",
"@types/koa-static": "^4.0.4", "@types/koa-static": "^4.0.4",
"@types/mithril": "^2.2.6", "@types/mithril": "^2.2.6",
"@types/node": "^20.11.29", "@types/node": "^20.11.30",
"@types/parsimmon": "^1.10.9", "@types/parsimmon": "^1.10.9",
"@types/seedrandom": "^3.0.8", "@types/seedrandom": "^3.0.8",
"@typescript-eslint/eslint-plugin": "^7.3.1", "@typescript-eslint/eslint-plugin": "^7.3.1",
@@ -62,7 +62,7 @@
"prettier": "^3.2.5", "prettier": "^3.2.5",
"sql.js": "^1.10.2", "sql.js": "^1.10.2",
"svgo": "^3.2.0", "svgo": "^3.2.0",
"typescript": "^5.4.2", "typescript": "^5.4.3",
"yaml": "^1.10.2" "yaml": "^1.10.2"
}, },
"engines": { "engines": {